diff options
| author | Nick Khyl <nickk@tailscale.com> | 2024-12-05 13:16:48 -0600 |
|---|---|---|
| committer | Nick Khyl <nickk@tailscale.com> | 2024-12-05 13:16:48 -0600 |
| commit | 0267fe83b200f1702a2fa0a395442c02a053fadb (patch) | |
| tree | 63654c55225eeb834de59a5a0bc8d19033c6145b /util | |
| parent | 87546a5edf6b6503a87eeb2d666baba57398a066 (diff) | |
| download | tailscale-1.78.0.tar.xz tailscale-1.78.0.zip | |
VERSION.txt: this is v1.78.0v1.78.0
Signed-off-by: Nick Khyl <nickk@tailscale.com>
Diffstat (limited to 'util')
64 files changed, 3409 insertions, 3409 deletions
diff --git a/util/cibuild/cibuild.go b/util/cibuild/cibuild.go index c1e337f9a..c3dee6154 100644 --- a/util/cibuild/cibuild.go +++ b/util/cibuild/cibuild.go @@ -1,14 +1,14 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -// Package cibuild reports runtime CI information. -package cibuild - -import "os" - -// On reports whether the current binary is executing on a CI system. -func On() bool { - // CI env variable is set by GitHub. - // https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables - return os.Getenv("GITHUB_ACTIONS") != "" || os.Getenv("CI") == "true" -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package cibuild reports runtime CI information.
+package cibuild
+
+import "os"
+
+// On reports whether the current binary is executing on a CI system.
+func On() bool {
+ // CI env variable is set by GitHub.
+ // https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables
+ return os.Getenv("GITHUB_ACTIONS") != "" || os.Getenv("CI") == "true"
+}
diff --git a/util/cstruct/cstruct.go b/util/cstruct/cstruct.go index 464dc5dc3..e32c90830 100644 --- a/util/cstruct/cstruct.go +++ b/util/cstruct/cstruct.go @@ -1,178 +1,178 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -// Package cstruct provides a helper for decoding binary data that is in the -// form of a padded C structure. -package cstruct - -import ( - "errors" - "io" - - "github.com/josharian/native" -) - -// Size of a pointer-typed value, in bits -const pointerSize = 32 << (^uintptr(0) >> 63) - -// We assume that non-64-bit platforms are 32-bit; we don't expect Go to run on -// a 16- or 8-bit architecture any time soon. -const is64Bit = pointerSize == 64 - -// Decoder reads and decodes padded fields from a slice of bytes. All fields -// are decoded with native endianness. -// -// Methods of a Decoder do not return errors, but rather store any error within -// the Decoder. The first error can be obtained via the Err method; after the -// first error, methods will return the zero value for their type. -type Decoder struct { - b []byte - off int - err error - dbuf [8]byte // for decoding -} - -// NewDecoder creates a Decoder from a byte slice. -func NewDecoder(b []byte) *Decoder { - return &Decoder{b: b} -} - -var errUnsupportedSize = errors.New("unsupported size") - -func padBytes(offset, size int) int { - if offset == 0 || size == 1 { - return 0 - } - remainder := offset % size - return size - remainder -} - -func (d *Decoder) getField(b []byte) error { - size := len(b) - - // We only support fields that are multiples of 2 (or 1-sized) - if size != 1 && size&1 == 1 { - return errUnsupportedSize - } - - // Fields are aligned to their size - padBytes := padBytes(d.off, size) - if d.off+size+padBytes > len(d.b) { - return io.EOF - } - d.off += padBytes - - copy(b, d.b[d.off:d.off+size]) - d.off += size - return nil -} - -// Err returns the first error that was encountered by this Decoder. -func (d *Decoder) Err() error { - return d.err -} - -// Offset returns the current read offset for data in the buffer. -func (d *Decoder) Offset() int { - return d.off -} - -// Byte returns a single byte from the buffer. -func (d *Decoder) Byte() byte { - if d.err != nil { - return 0 - } - - if err := d.getField(d.dbuf[0:1]); err != nil { - d.err = err - return 0 - } - return d.dbuf[0] -} - -// Byte returns a number of bytes from the buffer based on the size of the -// input slice. No padding is applied. -// -// If an error is encountered or this Decoder has previously encountered an -// error, no changes are made to the provided buffer. -func (d *Decoder) Bytes(b []byte) { - if d.err != nil { - return - } - - // No padding for byte slices - size := len(b) - if d.off+size >= len(d.b) { - d.err = io.EOF - return - } - copy(b, d.b[d.off:d.off+size]) - d.off += size -} - -// Uint16 returns a uint16 decoded from the buffer. -func (d *Decoder) Uint16() uint16 { - if d.err != nil { - return 0 - } - - if err := d.getField(d.dbuf[0:2]); err != nil { - d.err = err - return 0 - } - return native.Endian.Uint16(d.dbuf[0:2]) -} - -// Uint32 returns a uint32 decoded from the buffer. -func (d *Decoder) Uint32() uint32 { - if d.err != nil { - return 0 - } - - if err := d.getField(d.dbuf[0:4]); err != nil { - d.err = err - return 0 - } - return native.Endian.Uint32(d.dbuf[0:4]) -} - -// Uint64 returns a uint64 decoded from the buffer. -func (d *Decoder) Uint64() uint64 { - if d.err != nil { - return 0 - } - - if err := d.getField(d.dbuf[0:8]); err != nil { - d.err = err - return 0 - } - return native.Endian.Uint64(d.dbuf[0:8]) -} - -// Uintptr returns a uintptr decoded from the buffer. -func (d *Decoder) Uintptr() uintptr { - if d.err != nil { - return 0 - } - - if is64Bit { - return uintptr(d.Uint64()) - } else { - return uintptr(d.Uint32()) - } -} - -// Int16 returns a int16 decoded from the buffer. -func (d *Decoder) Int16() int16 { - return int16(d.Uint16()) -} - -// Int32 returns a int32 decoded from the buffer. -func (d *Decoder) Int32() int32 { - return int32(d.Uint32()) -} - -// Int64 returns a int64 decoded from the buffer. -func (d *Decoder) Int64() int64 { - return int64(d.Uint64()) -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package cstruct provides a helper for decoding binary data that is in the
+// form of a padded C structure.
+package cstruct
+
+import (
+ "errors"
+ "io"
+
+ "github.com/josharian/native"
+)
+
+// Size of a pointer-typed value, in bits
+const pointerSize = 32 << (^uintptr(0) >> 63)
+
+// We assume that non-64-bit platforms are 32-bit; we don't expect Go to run on
+// a 16- or 8-bit architecture any time soon.
+const is64Bit = pointerSize == 64
+
+// Decoder reads and decodes padded fields from a slice of bytes. All fields
+// are decoded with native endianness.
+//
+// Methods of a Decoder do not return errors, but rather store any error within
+// the Decoder. The first error can be obtained via the Err method; after the
+// first error, methods will return the zero value for their type.
+type Decoder struct {
+ b []byte
+ off int
+ err error
+ dbuf [8]byte // for decoding
+}
+
+// NewDecoder creates a Decoder from a byte slice.
+func NewDecoder(b []byte) *Decoder {
+ return &Decoder{b: b}
+}
+
+var errUnsupportedSize = errors.New("unsupported size")
+
+func padBytes(offset, size int) int {
+ if offset == 0 || size == 1 {
+ return 0
+ }
+ remainder := offset % size
+ return size - remainder
+}
+
+func (d *Decoder) getField(b []byte) error {
+ size := len(b)
+
+ // We only support fields that are multiples of 2 (or 1-sized)
+ if size != 1 && size&1 == 1 {
+ return errUnsupportedSize
+ }
+
+ // Fields are aligned to their size
+ padBytes := padBytes(d.off, size)
+ if d.off+size+padBytes > len(d.b) {
+ return io.EOF
+ }
+ d.off += padBytes
+
+ copy(b, d.b[d.off:d.off+size])
+ d.off += size
+ return nil
+}
+
+// Err returns the first error that was encountered by this Decoder.
+func (d *Decoder) Err() error {
+ return d.err
+}
+
+// Offset returns the current read offset for data in the buffer.
+func (d *Decoder) Offset() int {
+ return d.off
+}
+
+// Byte returns a single byte from the buffer.
+func (d *Decoder) Byte() byte {
+ if d.err != nil {
+ return 0
+ }
+
+ if err := d.getField(d.dbuf[0:1]); err != nil {
+ d.err = err
+ return 0
+ }
+ return d.dbuf[0]
+}
+
+// Byte returns a number of bytes from the buffer based on the size of the
+// input slice. No padding is applied.
+//
+// If an error is encountered or this Decoder has previously encountered an
+// error, no changes are made to the provided buffer.
+func (d *Decoder) Bytes(b []byte) {
+ if d.err != nil {
+ return
+ }
+
+ // No padding for byte slices
+ size := len(b)
+ if d.off+size >= len(d.b) {
+ d.err = io.EOF
+ return
+ }
+ copy(b, d.b[d.off:d.off+size])
+ d.off += size
+}
+
+// Uint16 returns a uint16 decoded from the buffer.
+func (d *Decoder) Uint16() uint16 {
+ if d.err != nil {
+ return 0
+ }
+
+ if err := d.getField(d.dbuf[0:2]); err != nil {
+ d.err = err
+ return 0
+ }
+ return native.Endian.Uint16(d.dbuf[0:2])
+}
+
+// Uint32 returns a uint32 decoded from the buffer.
+func (d *Decoder) Uint32() uint32 {
+ if d.err != nil {
+ return 0
+ }
+
+ if err := d.getField(d.dbuf[0:4]); err != nil {
+ d.err = err
+ return 0
+ }
+ return native.Endian.Uint32(d.dbuf[0:4])
+}
+
+// Uint64 returns a uint64 decoded from the buffer.
+func (d *Decoder) Uint64() uint64 {
+ if d.err != nil {
+ return 0
+ }
+
+ if err := d.getField(d.dbuf[0:8]); err != nil {
+ d.err = err
+ return 0
+ }
+ return native.Endian.Uint64(d.dbuf[0:8])
+}
+
+// Uintptr returns a uintptr decoded from the buffer.
+func (d *Decoder) Uintptr() uintptr {
+ if d.err != nil {
+ return 0
+ }
+
+ if is64Bit {
+ return uintptr(d.Uint64())
+ } else {
+ return uintptr(d.Uint32())
+ }
+}
+
+// Int16 returns a int16 decoded from the buffer.
+func (d *Decoder) Int16() int16 {
+ return int16(d.Uint16())
+}
+
+// Int32 returns a int32 decoded from the buffer.
+func (d *Decoder) Int32() int32 {
+ return int32(d.Uint32())
+}
+
+// Int64 returns a int64 decoded from the buffer.
+func (d *Decoder) Int64() int64 {
+ return int64(d.Uint64())
+}
diff --git a/util/cstruct/cstruct_example_test.go b/util/cstruct/cstruct_example_test.go index 17032267b..a36cbf9f0 100644 --- a/util/cstruct/cstruct_example_test.go +++ b/util/cstruct/cstruct_example_test.go @@ -1,73 +1,73 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -// Only built on 64-bit platforms to avoid complexity - -//go:build amd64 || arm64 || mips64le || ppc64le || riscv64 - -package cstruct - -import "fmt" - -// This test provides a semi-realistic example of how you can -// use this package to decode a C structure. -func ExampleDecoder() { - // Our example C structure: - // struct mystruct { - // char *p; - // char c; - // /* implicit: char _pad[3]; */ - // int x; - // }; - // - // The Go structure definition: - type myStruct struct { - Ptr uintptr - Ch byte - Intval uint32 - } - - // Our "in-memory" version of the above structure - buf := []byte{ - 1, 2, 3, 4, 0, 0, 0, 0, // ptr - 5, // ch - 99, 99, 99, // padding - 78, 6, 0, 0, // x - } - d := NewDecoder(buf) - - // Decode the structure; if one of these function returns an error, - // then subsequent decoder functions will return the zero value. - var x myStruct - x.Ptr = d.Uintptr() - x.Ch = d.Byte() - x.Intval = d.Uint32() - - // Note that per the Go language spec: - // [...] when evaluating the operands of an expression, assignment, - // or return statement, all function calls, method calls, and - // (channel) communication operations are evaluated in lexical - // left-to-right order - // - // Since each field is assigned via a function call, one could use the - // following snippet to decode the struct. - // x := myStruct{ - // Ptr: d.Uintptr(), - // Ch: d.Byte(), - // Intval: d.Uint32(), - // } - // - // However, this means that reordering the fields in the initialization - // statement–normally a semantically identical operation–would change - // the way the structure is parsed. Thus we do it as above with - // explicit ordering. - - // After finishing with the decoder, check errors - if err := d.Err(); err != nil { - panic(err) - } - - // Print the decoder offset and structure - fmt.Printf("off=%d struct=%#v\n", d.Offset(), x) - // Output: off=16 struct=cstruct.myStruct{Ptr:0x4030201, Ch:0x5, Intval:0x64e} -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Only built on 64-bit platforms to avoid complexity
+
+//go:build amd64 || arm64 || mips64le || ppc64le || riscv64
+
+package cstruct
+
+import "fmt"
+
+// This test provides a semi-realistic example of how you can
+// use this package to decode a C structure.
+func ExampleDecoder() {
+ // Our example C structure:
+ // struct mystruct {
+ // char *p;
+ // char c;
+ // /* implicit: char _pad[3]; */
+ // int x;
+ // };
+ //
+ // The Go structure definition:
+ type myStruct struct {
+ Ptr uintptr
+ Ch byte
+ Intval uint32
+ }
+
+ // Our "in-memory" version of the above structure
+ buf := []byte{
+ 1, 2, 3, 4, 0, 0, 0, 0, // ptr
+ 5, // ch
+ 99, 99, 99, // padding
+ 78, 6, 0, 0, // x
+ }
+ d := NewDecoder(buf)
+
+ // Decode the structure; if one of these function returns an error,
+ // then subsequent decoder functions will return the zero value.
+ var x myStruct
+ x.Ptr = d.Uintptr()
+ x.Ch = d.Byte()
+ x.Intval = d.Uint32()
+
+ // Note that per the Go language spec:
+ // [...] when evaluating the operands of an expression, assignment,
+ // or return statement, all function calls, method calls, and
+ // (channel) communication operations are evaluated in lexical
+ // left-to-right order
+ //
+ // Since each field is assigned via a function call, one could use the
+ // following snippet to decode the struct.
+ // x := myStruct{
+ // Ptr: d.Uintptr(),
+ // Ch: d.Byte(),
+ // Intval: d.Uint32(),
+ // }
+ //
+ // However, this means that reordering the fields in the initialization
+ // statement–normally a semantically identical operation–would change
+ // the way the structure is parsed. Thus we do it as above with
+ // explicit ordering.
+
+ // After finishing with the decoder, check errors
+ if err := d.Err(); err != nil {
+ panic(err)
+ }
+
+ // Print the decoder offset and structure
+ fmt.Printf("off=%d struct=%#v\n", d.Offset(), x)
+ // Output: off=16 struct=cstruct.myStruct{Ptr:0x4030201, Ch:0x5, Intval:0x64e}
+}
diff --git a/util/deephash/debug.go b/util/deephash/debug.go index 50b3d5605..ff417e583 100644 --- a/util/deephash/debug.go +++ b/util/deephash/debug.go @@ -1,37 +1,37 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -//go:build deephash_debug - -package deephash - -import "fmt" - -func (h *hasher) HashBytes(b []byte) { - fmt.Printf("B(%q)+", b) - h.Block512.HashBytes(b) -} -func (h *hasher) HashString(s string) { - fmt.Printf("S(%q)+", s) - h.Block512.HashString(s) -} -func (h *hasher) HashUint8(n uint8) { - fmt.Printf("U8(%d)+", n) - h.Block512.HashUint8(n) -} -func (h *hasher) HashUint16(n uint16) { - fmt.Printf("U16(%d)+", n) - h.Block512.HashUint16(n) -} -func (h *hasher) HashUint32(n uint32) { - fmt.Printf("U32(%d)+", n) - h.Block512.HashUint32(n) -} -func (h *hasher) HashUint64(n uint64) { - fmt.Printf("U64(%d)+", n) - h.Block512.HashUint64(n) -} -func (h *hasher) Sum(b []byte) []byte { - fmt.Println("FIN") - return h.Block512.Sum(b) -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build deephash_debug
+
+package deephash
+
+import "fmt"
+
+func (h *hasher) HashBytes(b []byte) {
+ fmt.Printf("B(%q)+", b)
+ h.Block512.HashBytes(b)
+}
+func (h *hasher) HashString(s string) {
+ fmt.Printf("S(%q)+", s)
+ h.Block512.HashString(s)
+}
+func (h *hasher) HashUint8(n uint8) {
+ fmt.Printf("U8(%d)+", n)
+ h.Block512.HashUint8(n)
+}
+func (h *hasher) HashUint16(n uint16) {
+ fmt.Printf("U16(%d)+", n)
+ h.Block512.HashUint16(n)
+}
+func (h *hasher) HashUint32(n uint32) {
+ fmt.Printf("U32(%d)+", n)
+ h.Block512.HashUint32(n)
+}
+func (h *hasher) HashUint64(n uint64) {
+ fmt.Printf("U64(%d)+", n)
+ h.Block512.HashUint64(n)
+}
+func (h *hasher) Sum(b []byte) []byte {
+ fmt.Println("FIN")
+ return h.Block512.Sum(b)
+}
diff --git a/util/deephash/pointer.go b/util/deephash/pointer.go index aafae47a2..71b11d7ff 100644 --- a/util/deephash/pointer.go +++ b/util/deephash/pointer.go @@ -1,114 +1,114 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package deephash - -import ( - "net/netip" - "reflect" - "time" - "unsafe" -) - -// unsafePointer is an untyped pointer. -// It is the caller's responsibility to call operations on the correct type. -// -// This pointer only ever points to a small set of kinds or types: -// time.Time, netip.Addr, string, array, slice, struct, map, pointer, interface, -// or a pointer to memory that is directly hashable. -// -// Arrays are represented as pointers to the first element. -// Structs are represented as pointers to the first field. -// Slices are represented as pointers to a slice header. -// Pointers are represented as pointers to a pointer. -// -// We do not support direct operations on maps and interfaces, and instead -// rely on pointer.asValue to convert the pointer back to a reflect.Value. -// Conversion of an unsafe.Pointer to reflect.Value guarantees that the -// read-only flag in the reflect.Value is unpopulated, avoiding panics that may -// otherwise have occurred since the value was obtained from an unexported field. -type unsafePointer struct{ p unsafe.Pointer } - -func unsafePointerOf(v reflect.Value) unsafePointer { - return unsafePointer{v.UnsafePointer()} -} -func (p unsafePointer) isNil() bool { - return p.p == nil -} - -// pointerElem dereferences a pointer. -// p must point to a pointer. -func (p unsafePointer) pointerElem() unsafePointer { - return unsafePointer{*(*unsafe.Pointer)(p.p)} -} - -// sliceLen returns the slice length. -// p must point to a slice. -func (p unsafePointer) sliceLen() int { - return (*reflect.SliceHeader)(p.p).Len -} - -// sliceArray returns a pointer to the underlying slice array. -// p must point to a slice. -func (p unsafePointer) sliceArray() unsafePointer { - return unsafePointer{unsafe.Pointer((*reflect.SliceHeader)(p.p).Data)} -} - -// arrayIndex returns a pointer to an element in the array. -// p must point to an array. -func (p unsafePointer) arrayIndex(index int, size uintptr) unsafePointer { - return unsafePointer{unsafe.Add(p.p, uintptr(index)*size)} -} - -// structField returns a pointer to a field in a struct. -// p must pointer to a struct. -func (p unsafePointer) structField(index int, offset, size uintptr) unsafePointer { - return unsafePointer{unsafe.Add(p.p, offset)} -} - -// asString casts p as a *string. -func (p unsafePointer) asString() *string { - return (*string)(p.p) -} - -// asTime casts p as a *time.Time. -func (p unsafePointer) asTime() *time.Time { - return (*time.Time)(p.p) -} - -// asAddr casts p as a *netip.Addr. -func (p unsafePointer) asAddr() *netip.Addr { - return (*netip.Addr)(p.p) -} - -// asValue casts p as a reflect.Value containing a pointer to value of t. -func (p unsafePointer) asValue(typ reflect.Type) reflect.Value { - return reflect.NewAt(typ, p.p) -} - -// asMemory returns the memory pointer at by p for a specified size. -func (p unsafePointer) asMemory(size uintptr) []byte { - return unsafe.Slice((*byte)(p.p), size) -} - -// visitStack is a stack of pointers visited. -// Pointers are pushed onto the stack when visited, and popped when leaving. -// The integer value is the depth at which the pointer was visited. -// The length of this stack should be zero after every hashing operation. -type visitStack map[unsafe.Pointer]int - -func (v visitStack) seen(p unsafe.Pointer) (int, bool) { - idx, ok := v[p] - return idx, ok -} - -func (v *visitStack) push(p unsafe.Pointer) { - if *v == nil { - *v = make(map[unsafe.Pointer]int) - } - (*v)[p] = len(*v) -} - -func (v visitStack) pop(p unsafe.Pointer) { - delete(v, p) -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package deephash
+
+import (
+ "net/netip"
+ "reflect"
+ "time"
+ "unsafe"
+)
+
+// unsafePointer is an untyped pointer.
+// It is the caller's responsibility to call operations on the correct type.
+//
+// This pointer only ever points to a small set of kinds or types:
+// time.Time, netip.Addr, string, array, slice, struct, map, pointer, interface,
+// or a pointer to memory that is directly hashable.
+//
+// Arrays are represented as pointers to the first element.
+// Structs are represented as pointers to the first field.
+// Slices are represented as pointers to a slice header.
+// Pointers are represented as pointers to a pointer.
+//
+// We do not support direct operations on maps and interfaces, and instead
+// rely on pointer.asValue to convert the pointer back to a reflect.Value.
+// Conversion of an unsafe.Pointer to reflect.Value guarantees that the
+// read-only flag in the reflect.Value is unpopulated, avoiding panics that may
+// otherwise have occurred since the value was obtained from an unexported field.
+type unsafePointer struct{ p unsafe.Pointer }
+
+func unsafePointerOf(v reflect.Value) unsafePointer {
+ return unsafePointer{v.UnsafePointer()}
+}
+func (p unsafePointer) isNil() bool {
+ return p.p == nil
+}
+
+// pointerElem dereferences a pointer.
+// p must point to a pointer.
+func (p unsafePointer) pointerElem() unsafePointer {
+ return unsafePointer{*(*unsafe.Pointer)(p.p)}
+}
+
+// sliceLen returns the slice length.
+// p must point to a slice.
+func (p unsafePointer) sliceLen() int {
+ return (*reflect.SliceHeader)(p.p).Len
+}
+
+// sliceArray returns a pointer to the underlying slice array.
+// p must point to a slice.
+func (p unsafePointer) sliceArray() unsafePointer {
+ return unsafePointer{unsafe.Pointer((*reflect.SliceHeader)(p.p).Data)}
+}
+
+// arrayIndex returns a pointer to an element in the array.
+// p must point to an array.
+func (p unsafePointer) arrayIndex(index int, size uintptr) unsafePointer {
+ return unsafePointer{unsafe.Add(p.p, uintptr(index)*size)}
+}
+
+// structField returns a pointer to a field in a struct.
+// p must pointer to a struct.
+func (p unsafePointer) structField(index int, offset, size uintptr) unsafePointer {
+ return unsafePointer{unsafe.Add(p.p, offset)}
+}
+
+// asString casts p as a *string.
+func (p unsafePointer) asString() *string {
+ return (*string)(p.p)
+}
+
+// asTime casts p as a *time.Time.
+func (p unsafePointer) asTime() *time.Time {
+ return (*time.Time)(p.p)
+}
+
+// asAddr casts p as a *netip.Addr.
+func (p unsafePointer) asAddr() *netip.Addr {
+ return (*netip.Addr)(p.p)
+}
+
+// asValue casts p as a reflect.Value containing a pointer to value of t.
+func (p unsafePointer) asValue(typ reflect.Type) reflect.Value {
+ return reflect.NewAt(typ, p.p)
+}
+
+// asMemory returns the memory pointer at by p for a specified size.
+func (p unsafePointer) asMemory(size uintptr) []byte {
+ return unsafe.Slice((*byte)(p.p), size)
+}
+
+// visitStack is a stack of pointers visited.
+// Pointers are pushed onto the stack when visited, and popped when leaving.
+// The integer value is the depth at which the pointer was visited.
+// The length of this stack should be zero after every hashing operation.
+type visitStack map[unsafe.Pointer]int
+
+func (v visitStack) seen(p unsafe.Pointer) (int, bool) {
+ idx, ok := v[p]
+ return idx, ok
+}
+
+func (v *visitStack) push(p unsafe.Pointer) {
+ if *v == nil {
+ *v = make(map[unsafe.Pointer]int)
+ }
+ (*v)[p] = len(*v)
+}
+
+func (v visitStack) pop(p unsafe.Pointer) {
+ delete(v, p)
+}
diff --git a/util/deephash/pointer_norace.go b/util/deephash/pointer_norace.go index f98a70f6a..499372000 100644 --- a/util/deephash/pointer_norace.go +++ b/util/deephash/pointer_norace.go @@ -1,13 +1,13 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -//go:build !race - -package deephash - -import "reflect" - -type pointer = unsafePointer - -// pointerOf returns a pointer from v, which must be a reflect.Pointer. -func pointerOf(v reflect.Value) pointer { return unsafePointerOf(v) } +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build !race
+
+package deephash
+
+import "reflect"
+
+type pointer = unsafePointer
+
+// pointerOf returns a pointer from v, which must be a reflect.Pointer.
+func pointerOf(v reflect.Value) pointer { return unsafePointerOf(v) }
diff --git a/util/deephash/pointer_race.go b/util/deephash/pointer_race.go index c638c7d39..93a358b6d 100644 --- a/util/deephash/pointer_race.go +++ b/util/deephash/pointer_race.go @@ -1,99 +1,99 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -//go:build race - -package deephash - -import ( - "fmt" - "net/netip" - "reflect" - "time" -) - -// pointer is a typed pointer that performs safety checks for every operation. -type pointer struct { - unsafePointer - t reflect.Type // type of pointed-at value; may be nil - n uintptr // size of valid memory after p -} - -// pointerOf returns a pointer from v, which must be a reflect.Pointer. -func pointerOf(v reflect.Value) pointer { - assert(v.Kind() == reflect.Pointer, "got %v, want pointer", v.Kind()) - te := v.Type().Elem() - return pointer{unsafePointerOf(v), te, te.Size()} -} - -func (p pointer) pointerElem() pointer { - assert(p.t.Kind() == reflect.Pointer, "got %v, want pointer", p.t.Kind()) - te := p.t.Elem() - return pointer{p.unsafePointer.pointerElem(), te, te.Size()} -} - -func (p pointer) sliceLen() int { - assert(p.t.Kind() == reflect.Slice, "got %v, want slice", p.t.Kind()) - return p.unsafePointer.sliceLen() -} - -func (p pointer) sliceArray() pointer { - assert(p.t.Kind() == reflect.Slice, "got %v, want slice", p.t.Kind()) - n := p.sliceLen() - assert(n >= 0, "got negative slice length %d", n) - ta := reflect.ArrayOf(n, p.t.Elem()) - return pointer{p.unsafePointer.sliceArray(), ta, ta.Size()} -} - -func (p pointer) arrayIndex(index int, size uintptr) pointer { - assert(p.t.Kind() == reflect.Array, "got %v, want array", p.t.Kind()) - assert(0 <= index && index < p.t.Len(), "got array of size %d, want to access element %d", p.t.Len(), index) - assert(p.t.Elem().Size() == size, "got element size of %d, want %d", p.t.Elem().Size(), size) - te := p.t.Elem() - return pointer{p.unsafePointer.arrayIndex(index, size), te, te.Size()} -} - -func (p pointer) structField(index int, offset, size uintptr) pointer { - assert(p.t.Kind() == reflect.Struct, "got %v, want struct", p.t.Kind()) - assert(p.n >= offset, "got size of %d, want excessive start offset of %d", p.n, offset) - assert(p.n >= offset+size, "got size of %d, want excessive end offset of %d", p.n, offset+size) - if index < 0 { - return pointer{p.unsafePointer.structField(index, offset, size), nil, size} - } - sf := p.t.Field(index) - t := sf.Type - assert(sf.Offset == offset, "got offset of %d, want offset %d", sf.Offset, offset) - assert(t.Size() == size, "got size of %d, want size %d", t.Size(), size) - return pointer{p.unsafePointer.structField(index, offset, size), t, t.Size()} -} - -func (p pointer) asString() *string { - assert(p.t.Kind() == reflect.String, "got %v, want string", p.t) - return p.unsafePointer.asString() -} - -func (p pointer) asTime() *time.Time { - assert(p.t == timeTimeType, "got %v, want %v", p.t, timeTimeType) - return p.unsafePointer.asTime() -} - -func (p pointer) asAddr() *netip.Addr { - assert(p.t == netipAddrType, "got %v, want %v", p.t, netipAddrType) - return p.unsafePointer.asAddr() -} - -func (p pointer) asValue(typ reflect.Type) reflect.Value { - assert(p.t == typ, "got %v, want %v", p.t, typ) - return p.unsafePointer.asValue(typ) -} - -func (p pointer) asMemory(size uintptr) []byte { - assert(p.n >= size, "got size of %d, want excessive size of %d", p.n, size) - return p.unsafePointer.asMemory(size) -} - -func assert(b bool, f string, a ...any) { - if !b { - panic(fmt.Sprintf(f, a...)) - } -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build race
+
+package deephash
+
+import (
+ "fmt"
+ "net/netip"
+ "reflect"
+ "time"
+)
+
+// pointer is a typed pointer that performs safety checks for every operation.
+type pointer struct {
+ unsafePointer
+ t reflect.Type // type of pointed-at value; may be nil
+ n uintptr // size of valid memory after p
+}
+
+// pointerOf returns a pointer from v, which must be a reflect.Pointer.
+func pointerOf(v reflect.Value) pointer {
+ assert(v.Kind() == reflect.Pointer, "got %v, want pointer", v.Kind())
+ te := v.Type().Elem()
+ return pointer{unsafePointerOf(v), te, te.Size()}
+}
+
+func (p pointer) pointerElem() pointer {
+ assert(p.t.Kind() == reflect.Pointer, "got %v, want pointer", p.t.Kind())
+ te := p.t.Elem()
+ return pointer{p.unsafePointer.pointerElem(), te, te.Size()}
+}
+
+func (p pointer) sliceLen() int {
+ assert(p.t.Kind() == reflect.Slice, "got %v, want slice", p.t.Kind())
+ return p.unsafePointer.sliceLen()
+}
+
+func (p pointer) sliceArray() pointer {
+ assert(p.t.Kind() == reflect.Slice, "got %v, want slice", p.t.Kind())
+ n := p.sliceLen()
+ assert(n >= 0, "got negative slice length %d", n)
+ ta := reflect.ArrayOf(n, p.t.Elem())
+ return pointer{p.unsafePointer.sliceArray(), ta, ta.Size()}
+}
+
+func (p pointer) arrayIndex(index int, size uintptr) pointer {
+ assert(p.t.Kind() == reflect.Array, "got %v, want array", p.t.Kind())
+ assert(0 <= index && index < p.t.Len(), "got array of size %d, want to access element %d", p.t.Len(), index)
+ assert(p.t.Elem().Size() == size, "got element size of %d, want %d", p.t.Elem().Size(), size)
+ te := p.t.Elem()
+ return pointer{p.unsafePointer.arrayIndex(index, size), te, te.Size()}
+}
+
+func (p pointer) structField(index int, offset, size uintptr) pointer {
+ assert(p.t.Kind() == reflect.Struct, "got %v, want struct", p.t.Kind())
+ assert(p.n >= offset, "got size of %d, want excessive start offset of %d", p.n, offset)
+ assert(p.n >= offset+size, "got size of %d, want excessive end offset of %d", p.n, offset+size)
+ if index < 0 {
+ return pointer{p.unsafePointer.structField(index, offset, size), nil, size}
+ }
+ sf := p.t.Field(index)
+ t := sf.Type
+ assert(sf.Offset == offset, "got offset of %d, want offset %d", sf.Offset, offset)
+ assert(t.Size() == size, "got size of %d, want size %d", t.Size(), size)
+ return pointer{p.unsafePointer.structField(index, offset, size), t, t.Size()}
+}
+
+func (p pointer) asString() *string {
+ assert(p.t.Kind() == reflect.String, "got %v, want string", p.t)
+ return p.unsafePointer.asString()
+}
+
+func (p pointer) asTime() *time.Time {
+ assert(p.t == timeTimeType, "got %v, want %v", p.t, timeTimeType)
+ return p.unsafePointer.asTime()
+}
+
+func (p pointer) asAddr() *netip.Addr {
+ assert(p.t == netipAddrType, "got %v, want %v", p.t, netipAddrType)
+ return p.unsafePointer.asAddr()
+}
+
+func (p pointer) asValue(typ reflect.Type) reflect.Value {
+ assert(p.t == typ, "got %v, want %v", p.t, typ)
+ return p.unsafePointer.asValue(typ)
+}
+
+func (p pointer) asMemory(size uintptr) []byte {
+ assert(p.n >= size, "got size of %d, want excessive size of %d", p.n, size)
+ return p.unsafePointer.asMemory(size)
+}
+
+func assert(b bool, f string, a ...any) {
+ if !b {
+ panic(fmt.Sprintf(f, a...))
+ }
+}
diff --git a/util/deephash/testtype/testtype.go b/util/deephash/testtype/testtype.go index 3c90053d6..2df38da87 100644 --- a/util/deephash/testtype/testtype.go +++ b/util/deephash/testtype/testtype.go @@ -1,15 +1,15 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -// Package testtype contains types for testing deephash. -package testtype - -import "time" - -type UnexportedAddressableTime struct { - t time.Time -} - -func NewUnexportedAddressableTime(t time.Time) *UnexportedAddressableTime { - return &UnexportedAddressableTime{t: t} -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package testtype contains types for testing deephash.
+package testtype
+
+import "time"
+
+type UnexportedAddressableTime struct {
+ t time.Time
+}
+
+func NewUnexportedAddressableTime(t time.Time) *UnexportedAddressableTime {
+ return &UnexportedAddressableTime{t: t}
+}
diff --git a/util/dirwalk/dirwalk.go b/util/dirwalk/dirwalk.go index 811766892..a05ee3553 100644 --- a/util/dirwalk/dirwalk.go +++ b/util/dirwalk/dirwalk.go @@ -1,53 +1,53 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -// Package dirwalk contains code to walk a directory. -package dirwalk - -import ( - "io" - "io/fs" - "os" - - "go4.org/mem" -) - -var osWalkShallow func(name mem.RO, fn WalkFunc) error - -// WalkFunc is the callback type used with WalkShallow. -// -// The name and de are only valid for the duration of func's call -// and should not be retained. -type WalkFunc func(name mem.RO, de fs.DirEntry) error - -// WalkShallow reads the entries in the named directory and calls fn for each. -// It does not recurse into subdirectories. -// -// If fn returns an error, iteration stops and WalkShallow returns that value. -// -// On Linux, WalkShallow does not allocate, so long as certain methods on the -// WalkFunc's DirEntry are not called which necessarily allocate. -func WalkShallow(dirName mem.RO, fn WalkFunc) error { - if f := osWalkShallow; f != nil { - return f(dirName, fn) - } - of, err := os.Open(dirName.StringCopy()) - if err != nil { - return err - } - defer of.Close() - for { - fis, err := of.ReadDir(100) - for _, de := range fis { - if err := fn(mem.S(de.Name()), de); err != nil { - return err - } - } - if err != nil { - if err == io.EOF { - return nil - } - return err - } - } -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package dirwalk contains code to walk a directory.
+package dirwalk
+
+import (
+ "io"
+ "io/fs"
+ "os"
+
+ "go4.org/mem"
+)
+
+var osWalkShallow func(name mem.RO, fn WalkFunc) error
+
+// WalkFunc is the callback type used with WalkShallow.
+//
+// The name and de are only valid for the duration of func's call
+// and should not be retained.
+type WalkFunc func(name mem.RO, de fs.DirEntry) error
+
+// WalkShallow reads the entries in the named directory and calls fn for each.
+// It does not recurse into subdirectories.
+//
+// If fn returns an error, iteration stops and WalkShallow returns that value.
+//
+// On Linux, WalkShallow does not allocate, so long as certain methods on the
+// WalkFunc's DirEntry are not called which necessarily allocate.
+func WalkShallow(dirName mem.RO, fn WalkFunc) error {
+ if f := osWalkShallow; f != nil {
+ return f(dirName, fn)
+ }
+ of, err := os.Open(dirName.StringCopy())
+ if err != nil {
+ return err
+ }
+ defer of.Close()
+ for {
+ fis, err := of.ReadDir(100)
+ for _, de := range fis {
+ if err := fn(mem.S(de.Name()), de); err != nil {
+ return err
+ }
+ }
+ if err != nil {
+ if err == io.EOF {
+ return nil
+ }
+ return err
+ }
+ }
+}
diff --git a/util/dirwalk/dirwalk_linux.go b/util/dirwalk/dirwalk_linux.go index 256467ebd..714783145 100644 --- a/util/dirwalk/dirwalk_linux.go +++ b/util/dirwalk/dirwalk_linux.go @@ -1,167 +1,167 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package dirwalk - -import ( - "fmt" - "io/fs" - "os" - "path/filepath" - "sync" - "syscall" - "unsafe" - - "go4.org/mem" - "golang.org/x/sys/unix" -) - -func init() { - osWalkShallow = linuxWalkShallow -} - -var dirEntPool = &sync.Pool{New: func() any { return new(linuxDirEnt) }} - -func linuxWalkShallow(dirName mem.RO, fn WalkFunc) error { - const blockSize = 8 << 10 - buf := make([]byte, blockSize) // stack-allocated; doesn't escape - - nameb := mem.Append(buf[:0], dirName) - nameb = append(nameb, 0) - - fd, err := sysOpen(nameb) - if err != nil { - return err - } - defer syscall.Close(fd) - - bufp := 0 // starting read position in buf - nbuf := 0 // end valid data in buf - - de := dirEntPool.Get().(*linuxDirEnt) - defer de.cleanAndPutInPool() - de.root = dirName - - for { - if bufp >= nbuf { - bufp = 0 - nbuf, err = readDirent(fd, buf) - if err != nil { - return err - } - if nbuf <= 0 { - return nil - } - } - consumed, name := parseDirEnt(&de.d, buf[bufp:nbuf]) - bufp += consumed - if len(name) == 0 || string(name) == "." || string(name) == ".." { - continue - } - de.name = mem.B(name) - if err := fn(de.name, de); err != nil { - return err - } - } -} - -type linuxDirEnt struct { - root mem.RO - d syscall.Dirent - name mem.RO -} - -func (de *linuxDirEnt) cleanAndPutInPool() { - de.root = mem.RO{} - de.name = mem.RO{} - dirEntPool.Put(de) -} - -func (de *linuxDirEnt) Name() string { return de.name.StringCopy() } -func (de *linuxDirEnt) Info() (fs.FileInfo, error) { - return os.Lstat(filepath.Join(de.root.StringCopy(), de.name.StringCopy())) -} -func (de *linuxDirEnt) IsDir() bool { - return de.d.Type == syscall.DT_DIR -} -func (de *linuxDirEnt) Type() fs.FileMode { - switch de.d.Type { - case syscall.DT_BLK: - return fs.ModeDevice // shrug - case syscall.DT_CHR: - return fs.ModeCharDevice - case syscall.DT_DIR: - return fs.ModeDir - case syscall.DT_FIFO: - return fs.ModeNamedPipe - case syscall.DT_LNK: - return fs.ModeSymlink - case syscall.DT_REG: - return 0 - case syscall.DT_SOCK: - return fs.ModeSocket - default: - return fs.ModeIrregular // shrug - } -} - -func direntNamlen(dirent *syscall.Dirent) int { - const fixedHdr = uint16(unsafe.Offsetof(syscall.Dirent{}.Name)) - limit := dirent.Reclen - fixedHdr - const dirNameLen = 256 // sizeof syscall.Dirent.Name - if limit > dirNameLen { - limit = dirNameLen - } - for i := uint16(0); i < limit; i++ { - if dirent.Name[i] == 0 { - return int(i) - } - } - panic("failed to find terminating 0 byte in dirent") -} - -func parseDirEnt(dirent *syscall.Dirent, buf []byte) (consumed int, name []byte) { - // golang.org/issue/37269 - copy(unsafe.Slice((*byte)(unsafe.Pointer(dirent)), unsafe.Sizeof(syscall.Dirent{})), buf) - if v := unsafe.Offsetof(dirent.Reclen) + unsafe.Sizeof(dirent.Reclen); uintptr(len(buf)) < v { - panic(fmt.Sprintf("buf size of %d smaller than dirent header size %d", len(buf), v)) - } - if len(buf) < int(dirent.Reclen) { - panic(fmt.Sprintf("buf size %d < record length %d", len(buf), dirent.Reclen)) - } - consumed = int(dirent.Reclen) - if dirent.Ino == 0 { // File absent in directory. - return - } - name = unsafe.Slice((*byte)(unsafe.Pointer(&dirent.Name[0])), direntNamlen(dirent)) - return -} - -func sysOpen(name []byte) (fd int, err error) { - if len(name) == 0 || name[len(name)-1] != 0 { - return 0, syscall.EINVAL - } - var dirfd int = unix.AT_FDCWD - for { - r0, _, e1 := syscall.Syscall(unix.SYS_OPENAT, uintptr(dirfd), - uintptr(unsafe.Pointer(&name[0])), 0) - if e1 == 0 { - return int(r0), nil - } - if e1 == syscall.EINTR { - // Since https://golang.org/doc/go1.14#runtime we - // need to loop on EINTR on more places. - continue - } - return 0, syscall.Errno(e1) - } -} - -func readDirent(fd int, buf []byte) (n int, err error) { - for { - nbuf, err := syscall.ReadDirent(fd, buf) - if err != syscall.EINTR { - return nbuf, err - } - } -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package dirwalk
+
+import (
+ "fmt"
+ "io/fs"
+ "os"
+ "path/filepath"
+ "sync"
+ "syscall"
+ "unsafe"
+
+ "go4.org/mem"
+ "golang.org/x/sys/unix"
+)
+
+func init() {
+ osWalkShallow = linuxWalkShallow
+}
+
+var dirEntPool = &sync.Pool{New: func() any { return new(linuxDirEnt) }}
+
+func linuxWalkShallow(dirName mem.RO, fn WalkFunc) error {
+ const blockSize = 8 << 10
+ buf := make([]byte, blockSize) // stack-allocated; doesn't escape
+
+ nameb := mem.Append(buf[:0], dirName)
+ nameb = append(nameb, 0)
+
+ fd, err := sysOpen(nameb)
+ if err != nil {
+ return err
+ }
+ defer syscall.Close(fd)
+
+ bufp := 0 // starting read position in buf
+ nbuf := 0 // end valid data in buf
+
+ de := dirEntPool.Get().(*linuxDirEnt)
+ defer de.cleanAndPutInPool()
+ de.root = dirName
+
+ for {
+ if bufp >= nbuf {
+ bufp = 0
+ nbuf, err = readDirent(fd, buf)
+ if err != nil {
+ return err
+ }
+ if nbuf <= 0 {
+ return nil
+ }
+ }
+ consumed, name := parseDirEnt(&de.d, buf[bufp:nbuf])
+ bufp += consumed
+ if len(name) == 0 || string(name) == "." || string(name) == ".." {
+ continue
+ }
+ de.name = mem.B(name)
+ if err := fn(de.name, de); err != nil {
+ return err
+ }
+ }
+}
+
+type linuxDirEnt struct {
+ root mem.RO
+ d syscall.Dirent
+ name mem.RO
+}
+
+func (de *linuxDirEnt) cleanAndPutInPool() {
+ de.root = mem.RO{}
+ de.name = mem.RO{}
+ dirEntPool.Put(de)
+}
+
+func (de *linuxDirEnt) Name() string { return de.name.StringCopy() }
+func (de *linuxDirEnt) Info() (fs.FileInfo, error) {
+ return os.Lstat(filepath.Join(de.root.StringCopy(), de.name.StringCopy()))
+}
+func (de *linuxDirEnt) IsDir() bool {
+ return de.d.Type == syscall.DT_DIR
+}
+func (de *linuxDirEnt) Type() fs.FileMode {
+ switch de.d.Type {
+ case syscall.DT_BLK:
+ return fs.ModeDevice // shrug
+ case syscall.DT_CHR:
+ return fs.ModeCharDevice
+ case syscall.DT_DIR:
+ return fs.ModeDir
+ case syscall.DT_FIFO:
+ return fs.ModeNamedPipe
+ case syscall.DT_LNK:
+ return fs.ModeSymlink
+ case syscall.DT_REG:
+ return 0
+ case syscall.DT_SOCK:
+ return fs.ModeSocket
+ default:
+ return fs.ModeIrregular // shrug
+ }
+}
+
+func direntNamlen(dirent *syscall.Dirent) int {
+ const fixedHdr = uint16(unsafe.Offsetof(syscall.Dirent{}.Name))
+ limit := dirent.Reclen - fixedHdr
+ const dirNameLen = 256 // sizeof syscall.Dirent.Name
+ if limit > dirNameLen {
+ limit = dirNameLen
+ }
+ for i := uint16(0); i < limit; i++ {
+ if dirent.Name[i] == 0 {
+ return int(i)
+ }
+ }
+ panic("failed to find terminating 0 byte in dirent")
+}
+
+func parseDirEnt(dirent *syscall.Dirent, buf []byte) (consumed int, name []byte) {
+ // golang.org/issue/37269
+ copy(unsafe.Slice((*byte)(unsafe.Pointer(dirent)), unsafe.Sizeof(syscall.Dirent{})), buf)
+ if v := unsafe.Offsetof(dirent.Reclen) + unsafe.Sizeof(dirent.Reclen); uintptr(len(buf)) < v {
+ panic(fmt.Sprintf("buf size of %d smaller than dirent header size %d", len(buf), v))
+ }
+ if len(buf) < int(dirent.Reclen) {
+ panic(fmt.Sprintf("buf size %d < record length %d", len(buf), dirent.Reclen))
+ }
+ consumed = int(dirent.Reclen)
+ if dirent.Ino == 0 { // File absent in directory.
+ return
+ }
+ name = unsafe.Slice((*byte)(unsafe.Pointer(&dirent.Name[0])), direntNamlen(dirent))
+ return
+}
+
+func sysOpen(name []byte) (fd int, err error) {
+ if len(name) == 0 || name[len(name)-1] != 0 {
+ return 0, syscall.EINVAL
+ }
+ var dirfd int = unix.AT_FDCWD
+ for {
+ r0, _, e1 := syscall.Syscall(unix.SYS_OPENAT, uintptr(dirfd),
+ uintptr(unsafe.Pointer(&name[0])), 0)
+ if e1 == 0 {
+ return int(r0), nil
+ }
+ if e1 == syscall.EINTR {
+ // Since https://golang.org/doc/go1.14#runtime we
+ // need to loop on EINTR on more places.
+ continue
+ }
+ return 0, syscall.Errno(e1)
+ }
+}
+
+func readDirent(fd int, buf []byte) (n int, err error) {
+ for {
+ nbuf, err := syscall.ReadDirent(fd, buf)
+ if err != syscall.EINTR {
+ return nbuf, err
+ }
+ }
+}
diff --git a/util/dirwalk/dirwalk_test.go b/util/dirwalk/dirwalk_test.go index 15ebc13dd..e2e41f634 100644 --- a/util/dirwalk/dirwalk_test.go +++ b/util/dirwalk/dirwalk_test.go @@ -1,91 +1,91 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package dirwalk - -import ( - "fmt" - "os" - "path/filepath" - "reflect" - "runtime" - "sort" - "testing" - - "go4.org/mem" - "tailscale.com/tstest" -) - -func TestWalkShallowOSSpecific(t *testing.T) { - if osWalkShallow == nil { - t.Skip("no OS-specific implementation") - } - testWalkShallow(t, false) -} - -func TestWalkShallowPortable(t *testing.T) { - testWalkShallow(t, true) -} - -func testWalkShallow(t *testing.T, portable bool) { - if portable { - tstest.Replace(t, &osWalkShallow, nil) - } - d := t.TempDir() - - t.Run("basics", func(t *testing.T) { - if err := os.WriteFile(filepath.Join(d, "foo"), []byte("1"), 0600); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(d, "bar"), []byte("22"), 0400); err != nil { - t.Fatal(err) - } - if err := os.Mkdir(filepath.Join(d, "baz"), 0777); err != nil { - t.Fatal(err) - } - - var got []string - if err := WalkShallow(mem.S(d), func(name mem.RO, de os.DirEntry) error { - var size int64 - if fi, err := de.Info(); err != nil { - t.Errorf("Info stat error on %q: %v", de.Name(), err) - } else if !fi.IsDir() { - size = fi.Size() - } - got = append(got, fmt.Sprintf("%q %q dir=%v type=%d size=%v", name.StringCopy(), de.Name(), de.IsDir(), de.Type(), size)) - return nil - }); err != nil { - t.Fatal(err) - } - sort.Strings(got) - want := []string{ - `"bar" "bar" dir=false type=0 size=2`, - `"baz" "baz" dir=true type=2147483648 size=0`, - `"foo" "foo" dir=false type=0 size=1`, - } - if !reflect.DeepEqual(got, want) { - t.Errorf("mismatch:\n got %#q\nwant %#q", got, want) - } - }) - - t.Run("err_not_exist", func(t *testing.T) { - err := WalkShallow(mem.S(filepath.Join(d, "not_exist")), func(name mem.RO, de os.DirEntry) error { - return nil - }) - if !os.IsNotExist(err) { - t.Errorf("unexpected error: %v", err) - } - }) - - t.Run("allocs", func(t *testing.T) { - allocs := int(testing.AllocsPerRun(1000, func() { - if err := WalkShallow(mem.S(d), func(name mem.RO, de os.DirEntry) error { return nil }); err != nil { - t.Fatal(err) - } - })) - t.Logf("allocs = %v", allocs) - if !portable && runtime.GOOS == "linux" && allocs != 0 { - t.Errorf("unexpected allocs: got %v, want 0", allocs) - } - }) -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package dirwalk
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "reflect"
+ "runtime"
+ "sort"
+ "testing"
+
+ "go4.org/mem"
+ "tailscale.com/tstest"
+)
+
+func TestWalkShallowOSSpecific(t *testing.T) {
+ if osWalkShallow == nil {
+ t.Skip("no OS-specific implementation")
+ }
+ testWalkShallow(t, false)
+}
+
+func TestWalkShallowPortable(t *testing.T) {
+ testWalkShallow(t, true)
+}
+
+func testWalkShallow(t *testing.T, portable bool) {
+ if portable {
+ tstest.Replace(t, &osWalkShallow, nil)
+ }
+ d := t.TempDir()
+
+ t.Run("basics", func(t *testing.T) {
+ if err := os.WriteFile(filepath.Join(d, "foo"), []byte("1"), 0600); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.WriteFile(filepath.Join(d, "bar"), []byte("22"), 0400); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.Mkdir(filepath.Join(d, "baz"), 0777); err != nil {
+ t.Fatal(err)
+ }
+
+ var got []string
+ if err := WalkShallow(mem.S(d), func(name mem.RO, de os.DirEntry) error {
+ var size int64
+ if fi, err := de.Info(); err != nil {
+ t.Errorf("Info stat error on %q: %v", de.Name(), err)
+ } else if !fi.IsDir() {
+ size = fi.Size()
+ }
+ got = append(got, fmt.Sprintf("%q %q dir=%v type=%d size=%v", name.StringCopy(), de.Name(), de.IsDir(), de.Type(), size))
+ return nil
+ }); err != nil {
+ t.Fatal(err)
+ }
+ sort.Strings(got)
+ want := []string{
+ `"bar" "bar" dir=false type=0 size=2`,
+ `"baz" "baz" dir=true type=2147483648 size=0`,
+ `"foo" "foo" dir=false type=0 size=1`,
+ }
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("mismatch:\n got %#q\nwant %#q", got, want)
+ }
+ })
+
+ t.Run("err_not_exist", func(t *testing.T) {
+ err := WalkShallow(mem.S(filepath.Join(d, "not_exist")), func(name mem.RO, de os.DirEntry) error {
+ return nil
+ })
+ if !os.IsNotExist(err) {
+ t.Errorf("unexpected error: %v", err)
+ }
+ })
+
+ t.Run("allocs", func(t *testing.T) {
+ allocs := int(testing.AllocsPerRun(1000, func() {
+ if err := WalkShallow(mem.S(d), func(name mem.RO, de os.DirEntry) error { return nil }); err != nil {
+ t.Fatal(err)
+ }
+ }))
+ t.Logf("allocs = %v", allocs)
+ if !portable && runtime.GOOS == "linux" && allocs != 0 {
+ t.Errorf("unexpected allocs: got %v, want 0", allocs)
+ }
+ })
+}
diff --git a/util/goroutines/goroutines.go b/util/goroutines/goroutines.go index 9758b0758..24c61b37c 100644 --- a/util/goroutines/goroutines.go +++ b/util/goroutines/goroutines.go @@ -1,93 +1,93 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -// The goroutines package contains utilities for getting active goroutines. -package goroutines - -import ( - "bytes" - "fmt" - "runtime" - "strconv" -) - -// ScrubbedGoroutineDump returns either the current goroutine's stack or all -// goroutines' stacks, but with the actual values of arguments scrubbed out, -// lest it contain some private key material. -func ScrubbedGoroutineDump(all bool) []byte { - var buf []byte - // Grab stacks multiple times into increasingly larger buffer sizes - // to minimize the risk that we blow past our iOS memory limit. - for size := 1 << 10; size <= 1<<20; size += 1 << 10 { - buf = make([]byte, size) - buf = buf[:runtime.Stack(buf, all)] - if len(buf) < size { - // It fit. - break - } - } - return scrubHex(buf) -} - -func scrubHex(buf []byte) []byte { - saw := map[string][]byte{} // "0x123" => "v1%3" (unique value 1 and its value mod 8) - - foreachHexAddress(buf, func(in []byte) { - if string(in) == "0x0" { - return - } - if v, ok := saw[string(in)]; ok { - for i := range in { - in[i] = '_' - } - copy(in, v) - return - } - inStr := string(in) - u64, err := strconv.ParseUint(string(in[2:]), 16, 64) - for i := range in { - in[i] = '_' - } - if err != nil { - in[0] = '?' - return - } - v := []byte(fmt.Sprintf("v%d%%%d", len(saw)+1, u64%8)) - saw[inStr] = v - copy(in, v) - }) - return buf -} - -var ohx = []byte("0x") - -// foreachHexAddress calls f with each subslice of b that matches -// regexp `0x[0-9a-f]*`. -func foreachHexAddress(b []byte, f func([]byte)) { - for len(b) > 0 { - i := bytes.Index(b, ohx) - if i == -1 { - return - } - b = b[i:] - hx := hexPrefix(b) - f(hx) - b = b[len(hx):] - } -} - -func hexPrefix(b []byte) []byte { - for i, c := range b { - if i < 2 { - continue - } - if !isHexByte(c) { - return b[:i] - } - } - return b -} - -func isHexByte(b byte) bool { - return '0' <= b && b <= '9' || 'a' <= b && b <= 'f' || 'A' <= b && b <= 'F' -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// The goroutines package contains utilities for getting active goroutines.
+package goroutines
+
+import (
+ "bytes"
+ "fmt"
+ "runtime"
+ "strconv"
+)
+
+// ScrubbedGoroutineDump returns either the current goroutine's stack or all
+// goroutines' stacks, but with the actual values of arguments scrubbed out,
+// lest it contain some private key material.
+func ScrubbedGoroutineDump(all bool) []byte {
+ var buf []byte
+ // Grab stacks multiple times into increasingly larger buffer sizes
+ // to minimize the risk that we blow past our iOS memory limit.
+ for size := 1 << 10; size <= 1<<20; size += 1 << 10 {
+ buf = make([]byte, size)
+ buf = buf[:runtime.Stack(buf, all)]
+ if len(buf) < size {
+ // It fit.
+ break
+ }
+ }
+ return scrubHex(buf)
+}
+
+func scrubHex(buf []byte) []byte {
+ saw := map[string][]byte{} // "0x123" => "v1%3" (unique value 1 and its value mod 8)
+
+ foreachHexAddress(buf, func(in []byte) {
+ if string(in) == "0x0" {
+ return
+ }
+ if v, ok := saw[string(in)]; ok {
+ for i := range in {
+ in[i] = '_'
+ }
+ copy(in, v)
+ return
+ }
+ inStr := string(in)
+ u64, err := strconv.ParseUint(string(in[2:]), 16, 64)
+ for i := range in {
+ in[i] = '_'
+ }
+ if err != nil {
+ in[0] = '?'
+ return
+ }
+ v := []byte(fmt.Sprintf("v%d%%%d", len(saw)+1, u64%8))
+ saw[inStr] = v
+ copy(in, v)
+ })
+ return buf
+}
+
+var ohx = []byte("0x")
+
+// foreachHexAddress calls f with each subslice of b that matches
+// regexp `0x[0-9a-f]*`.
+func foreachHexAddress(b []byte, f func([]byte)) {
+ for len(b) > 0 {
+ i := bytes.Index(b, ohx)
+ if i == -1 {
+ return
+ }
+ b = b[i:]
+ hx := hexPrefix(b)
+ f(hx)
+ b = b[len(hx):]
+ }
+}
+
+func hexPrefix(b []byte) []byte {
+ for i, c := range b {
+ if i < 2 {
+ continue
+ }
+ if !isHexByte(c) {
+ return b[:i]
+ }
+ }
+ return b
+}
+
+func isHexByte(b byte) bool {
+ return '0' <= b && b <= '9' || 'a' <= b && b <= 'f' || 'A' <= b && b <= 'F'
+}
diff --git a/util/goroutines/goroutines_test.go b/util/goroutines/goroutines_test.go index ae17c399c..df6560fe5 100644 --- a/util/goroutines/goroutines_test.go +++ b/util/goroutines/goroutines_test.go @@ -1,29 +1,29 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package goroutines - -import "testing" - -func TestScrubbedGoroutineDump(t *testing.T) { - t.Logf("Got:\n%s\n", ScrubbedGoroutineDump(true)) -} - -func TestScrubHex(t *testing.T) { - tests := []struct { - in, want string - }{ - {"foo", "foo"}, - {"", ""}, - {"0x", "?_"}, - {"0x001 and same 0x001", "v1%1_ and same v1%1_"}, - {"0x008 and same 0x008", "v1%0_ and same v1%0_"}, - {"0x001 and diff 0x002", "v1%1_ and diff v2%2_"}, - } - for _, tt := range tests { - got := scrubHex([]byte(tt.in)) - if string(got) != tt.want { - t.Errorf("for input:\n%s\n\ngot:\n%s\n\nwant:\n%s\n", tt.in, got, tt.want) - } - } -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package goroutines
+
+import "testing"
+
+func TestScrubbedGoroutineDump(t *testing.T) {
+ t.Logf("Got:\n%s\n", ScrubbedGoroutineDump(true))
+}
+
+func TestScrubHex(t *testing.T) {
+ tests := []struct {
+ in, want string
+ }{
+ {"foo", "foo"},
+ {"", ""},
+ {"0x", "?_"},
+ {"0x001 and same 0x001", "v1%1_ and same v1%1_"},
+ {"0x008 and same 0x008", "v1%0_ and same v1%0_"},
+ {"0x001 and diff 0x002", "v1%1_ and diff v2%2_"},
+ }
+ for _, tt := range tests {
+ got := scrubHex([]byte(tt.in))
+ if string(got) != tt.want {
+ t.Errorf("for input:\n%s\n\ngot:\n%s\n\nwant:\n%s\n", tt.in, got, tt.want)
+ }
+ }
+}
diff --git a/util/groupmember/groupmember.go b/util/groupmember/groupmember.go index d60416816..38431a7ff 100644 --- a/util/groupmember/groupmember.go +++ b/util/groupmember/groupmember.go @@ -1,29 +1,29 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -// Package groupmember verifies group membership of the provided user on the -// local system. -package groupmember - -import ( - "os/user" - "slices" -) - -// IsMemberOfGroup reports whether the provided user is a member of -// the provided system group. -func IsMemberOfGroup(group, userName string) (bool, error) { - u, err := user.Lookup(userName) - if err != nil { - return false, err - } - g, err := user.LookupGroup(group) - if err != nil { - return false, err - } - ugids, err := u.GroupIds() - if err != nil { - return false, err - } - return slices.Contains(ugids, g.Gid), nil -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package groupmember verifies group membership of the provided user on the
+// local system.
+package groupmember
+
+import (
+ "os/user"
+ "slices"
+)
+
+// IsMemberOfGroup reports whether the provided user is a member of
+// the provided system group.
+func IsMemberOfGroup(group, userName string) (bool, error) {
+ u, err := user.Lookup(userName)
+ if err != nil {
+ return false, err
+ }
+ g, err := user.LookupGroup(group)
+ if err != nil {
+ return false, err
+ }
+ ugids, err := u.GroupIds()
+ if err != nil {
+ return false, err
+ }
+ return slices.Contains(ugids, g.Gid), nil
+}
diff --git a/util/hashx/block512.go b/util/hashx/block512.go index e637c0c03..dd69ccd35 100644 --- a/util/hashx/block512.go +++ b/util/hashx/block512.go @@ -1,197 +1,197 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -// Package hashx provides a concrete implementation of [hash.Hash] -// that operates on a particular block size. -package hashx - -import ( - "encoding/binary" - "fmt" - "hash" - "unsafe" -) - -var _ hash.Hash = (*Block512)(nil) - -// Block512 wraps a [hash.Hash] for functions that operate on 512-bit block sizes. -// It has efficient methods for hashing fixed-width integers. -// -// A hashing algorithm that operates on 512-bit block sizes should be used. -// The hash still operates correctly even with misaligned block sizes, -// but operates less efficiently. -// -// Example algorithms with 512-bit block sizes include: -// - MD4 (https://golang.org/x/crypto/md4) -// - MD5 (https://golang.org/pkg/crypto/md5) -// - BLAKE2s (https://golang.org/x/crypto/blake2s) -// - BLAKE3 -// - RIPEMD (https://golang.org/x/crypto/ripemd160) -// - SHA-0 -// - SHA-1 (https://golang.org/pkg/crypto/sha1) -// - SHA-2 (https://golang.org/pkg/crypto/sha256) -// - Whirlpool -// -// See https://en.wikipedia.org/wiki/Comparison_of_cryptographic_hash_functions#Parameters -// for a list of hash functions and their block sizes. -// -// Block512 assumes that [hash.Hash.Write] never fails and -// never allows the provided buffer to escape. -type Block512 struct { - hash.Hash - - x [512 / 8]byte - nx int -} - -// New512 constructs a new Block512 that wraps h. -// -// It reports an error if the block sizes do not match. -// Misaligned block sizes perform poorly, but execute correctly. -// The error may be ignored if performance is not a concern. -func New512(h hash.Hash) (*Block512, error) { - b := &Block512{Hash: h} - if len(b.x)%h.BlockSize() != 0 { - return b, fmt.Errorf("hashx.Block512: inefficient use of hash.Hash with %d-bit block size", 8*h.BlockSize()) - } - return b, nil -} - -// Write hashes the contents of b. -func (h *Block512) Write(b []byte) (int, error) { - h.HashBytes(b) - return len(b), nil -} - -// Sum appends the current hash to b and returns the resulting slice. -// -// It flushes any partially completed blocks to the underlying [hash.Hash], -// which may cause future operations to be misaligned and less efficient -// until [Block512.Reset] is called. -func (h *Block512) Sum(b []byte) []byte { - if h.nx > 0 { - h.Hash.Write(h.x[:h.nx]) - h.nx = 0 - } - - // Unfortunately hash.Hash.Sum always causes the input to escape since - // escape analysis cannot prove anything past an interface method call. - // Assuming h already escapes, we call Sum with h.x first, - // and then copy the result to b. - sum := h.Hash.Sum(h.x[:0]) - return append(b, sum...) -} - -// Reset resets Block512 to its initial state. -// It recursively resets the underlying [hash.Hash]. -func (h *Block512) Reset() { - h.Hash.Reset() - h.nx = 0 -} - -// HashUint8 hashes n as a 1-byte integer. -func (h *Block512) HashUint8(n uint8) { - // NOTE: This method is carefully written to be inlineable. - if h.nx <= len(h.x)-1 { - h.x[h.nx] = n - h.nx += 1 - } else { - h.hashUint8Slow(n) // mark "noinline" to keep this within inline budget - } -} - -//go:noinline -func (h *Block512) hashUint8Slow(n uint8) { h.hashUint(uint64(n), 1) } - -// HashUint16 hashes n as a 2-byte little-endian integer. -func (h *Block512) HashUint16(n uint16) { - // NOTE: This method is carefully written to be inlineable. - if h.nx <= len(h.x)-2 { - binary.LittleEndian.PutUint16(h.x[h.nx:], n) - h.nx += 2 - } else { - h.hashUint16Slow(n) // mark "noinline" to keep this within inline budget - } -} - -//go:noinline -func (h *Block512) hashUint16Slow(n uint16) { h.hashUint(uint64(n), 2) } - -// HashUint32 hashes n as a 4-byte little-endian integer. -func (h *Block512) HashUint32(n uint32) { - // NOTE: This method is carefully written to be inlineable. - if h.nx <= len(h.x)-4 { - binary.LittleEndian.PutUint32(h.x[h.nx:], n) - h.nx += 4 - } else { - h.hashUint32Slow(n) // mark "noinline" to keep this within inline budget - } -} - -//go:noinline -func (h *Block512) hashUint32Slow(n uint32) { h.hashUint(uint64(n), 4) } - -// HashUint64 hashes n as a 8-byte little-endian integer. -func (h *Block512) HashUint64(n uint64) { - // NOTE: This method is carefully written to be inlineable. - if h.nx <= len(h.x)-8 { - binary.LittleEndian.PutUint64(h.x[h.nx:], n) - h.nx += 8 - } else { - h.hashUint64Slow(n) // mark "noinline" to keep this within inline budget - } -} - -//go:noinline -func (h *Block512) hashUint64Slow(n uint64) { h.hashUint(uint64(n), 8) } - -func (h *Block512) hashUint(n uint64, i int) { - for ; i > 0; i-- { - if h.nx == len(h.x) { - h.Hash.Write(h.x[:]) - h.nx = 0 - } - h.x[h.nx] = byte(n) - h.nx += 1 - n >>= 8 - } -} - -// HashBytes hashes the contents of b. -// It does not explicitly hash the length separately. -func (h *Block512) HashBytes(b []byte) { - // Nearly identical to sha256.digest.Write. - if h.nx > 0 { - n := copy(h.x[h.nx:], b) - h.nx += n - if h.nx == len(h.x) { - h.Hash.Write(h.x[:]) - h.nx = 0 - } - b = b[n:] - } - if len(b) >= len(h.x) { - n := len(b) &^ (len(h.x) - 1) // n is a multiple of len(h.x) - h.Hash.Write(b[:n]) - b = b[n:] - } - if len(b) > 0 { - h.nx = copy(h.x[:], b) - } -} - -// HashString hashes the contents of s. -// It does not explicitly hash the length separately. -func (h *Block512) HashString(s string) { - // TODO: Avoid unsafe when standard hashers implement io.StringWriter. - // See https://go.dev/issue/38776. - type stringHeader struct { - p unsafe.Pointer - n int - } - p := (*stringHeader)(unsafe.Pointer(&s)) - b := unsafe.Slice((*byte)(p.p), p.n) - h.HashBytes(b) -} - -// TODO: Add Hash.MarshalBinary and Hash.UnmarshalBinary? +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package hashx provides a concrete implementation of [hash.Hash]
+// that operates on a particular block size.
+package hashx
+
+import (
+ "encoding/binary"
+ "fmt"
+ "hash"
+ "unsafe"
+)
+
+var _ hash.Hash = (*Block512)(nil)
+
+// Block512 wraps a [hash.Hash] for functions that operate on 512-bit block sizes.
+// It has efficient methods for hashing fixed-width integers.
+//
+// A hashing algorithm that operates on 512-bit block sizes should be used.
+// The hash still operates correctly even with misaligned block sizes,
+// but operates less efficiently.
+//
+// Example algorithms with 512-bit block sizes include:
+// - MD4 (https://golang.org/x/crypto/md4)
+// - MD5 (https://golang.org/pkg/crypto/md5)
+// - BLAKE2s (https://golang.org/x/crypto/blake2s)
+// - BLAKE3
+// - RIPEMD (https://golang.org/x/crypto/ripemd160)
+// - SHA-0
+// - SHA-1 (https://golang.org/pkg/crypto/sha1)
+// - SHA-2 (https://golang.org/pkg/crypto/sha256)
+// - Whirlpool
+//
+// See https://en.wikipedia.org/wiki/Comparison_of_cryptographic_hash_functions#Parameters
+// for a list of hash functions and their block sizes.
+//
+// Block512 assumes that [hash.Hash.Write] never fails and
+// never allows the provided buffer to escape.
+type Block512 struct {
+ hash.Hash
+
+ x [512 / 8]byte
+ nx int
+}
+
+// New512 constructs a new Block512 that wraps h.
+//
+// It reports an error if the block sizes do not match.
+// Misaligned block sizes perform poorly, but execute correctly.
+// The error may be ignored if performance is not a concern.
+func New512(h hash.Hash) (*Block512, error) {
+ b := &Block512{Hash: h}
+ if len(b.x)%h.BlockSize() != 0 {
+ return b, fmt.Errorf("hashx.Block512: inefficient use of hash.Hash with %d-bit block size", 8*h.BlockSize())
+ }
+ return b, nil
+}
+
+// Write hashes the contents of b.
+func (h *Block512) Write(b []byte) (int, error) {
+ h.HashBytes(b)
+ return len(b), nil
+}
+
+// Sum appends the current hash to b and returns the resulting slice.
+//
+// It flushes any partially completed blocks to the underlying [hash.Hash],
+// which may cause future operations to be misaligned and less efficient
+// until [Block512.Reset] is called.
+func (h *Block512) Sum(b []byte) []byte {
+ if h.nx > 0 {
+ h.Hash.Write(h.x[:h.nx])
+ h.nx = 0
+ }
+
+ // Unfortunately hash.Hash.Sum always causes the input to escape since
+ // escape analysis cannot prove anything past an interface method call.
+ // Assuming h already escapes, we call Sum with h.x first,
+ // and then copy the result to b.
+ sum := h.Hash.Sum(h.x[:0])
+ return append(b, sum...)
+}
+
+// Reset resets Block512 to its initial state.
+// It recursively resets the underlying [hash.Hash].
+func (h *Block512) Reset() {
+ h.Hash.Reset()
+ h.nx = 0
+}
+
+// HashUint8 hashes n as a 1-byte integer.
+func (h *Block512) HashUint8(n uint8) {
+ // NOTE: This method is carefully written to be inlineable.
+ if h.nx <= len(h.x)-1 {
+ h.x[h.nx] = n
+ h.nx += 1
+ } else {
+ h.hashUint8Slow(n) // mark "noinline" to keep this within inline budget
+ }
+}
+
+//go:noinline
+func (h *Block512) hashUint8Slow(n uint8) { h.hashUint(uint64(n), 1) }
+
+// HashUint16 hashes n as a 2-byte little-endian integer.
+func (h *Block512) HashUint16(n uint16) {
+ // NOTE: This method is carefully written to be inlineable.
+ if h.nx <= len(h.x)-2 {
+ binary.LittleEndian.PutUint16(h.x[h.nx:], n)
+ h.nx += 2
+ } else {
+ h.hashUint16Slow(n) // mark "noinline" to keep this within inline budget
+ }
+}
+
+//go:noinline
+func (h *Block512) hashUint16Slow(n uint16) { h.hashUint(uint64(n), 2) }
+
+// HashUint32 hashes n as a 4-byte little-endian integer.
+func (h *Block512) HashUint32(n uint32) {
+ // NOTE: This method is carefully written to be inlineable.
+ if h.nx <= len(h.x)-4 {
+ binary.LittleEndian.PutUint32(h.x[h.nx:], n)
+ h.nx += 4
+ } else {
+ h.hashUint32Slow(n) // mark "noinline" to keep this within inline budget
+ }
+}
+
+//go:noinline
+func (h *Block512) hashUint32Slow(n uint32) { h.hashUint(uint64(n), 4) }
+
+// HashUint64 hashes n as a 8-byte little-endian integer.
+func (h *Block512) HashUint64(n uint64) {
+ // NOTE: This method is carefully written to be inlineable.
+ if h.nx <= len(h.x)-8 {
+ binary.LittleEndian.PutUint64(h.x[h.nx:], n)
+ h.nx += 8
+ } else {
+ h.hashUint64Slow(n) // mark "noinline" to keep this within inline budget
+ }
+}
+
+//go:noinline
+func (h *Block512) hashUint64Slow(n uint64) { h.hashUint(uint64(n), 8) }
+
+func (h *Block512) hashUint(n uint64, i int) {
+ for ; i > 0; i-- {
+ if h.nx == len(h.x) {
+ h.Hash.Write(h.x[:])
+ h.nx = 0
+ }
+ h.x[h.nx] = byte(n)
+ h.nx += 1
+ n >>= 8
+ }
+}
+
+// HashBytes hashes the contents of b.
+// It does not explicitly hash the length separately.
+func (h *Block512) HashBytes(b []byte) {
+ // Nearly identical to sha256.digest.Write.
+ if h.nx > 0 {
+ n := copy(h.x[h.nx:], b)
+ h.nx += n
+ if h.nx == len(h.x) {
+ h.Hash.Write(h.x[:])
+ h.nx = 0
+ }
+ b = b[n:]
+ }
+ if len(b) >= len(h.x) {
+ n := len(b) &^ (len(h.x) - 1) // n is a multiple of len(h.x)
+ h.Hash.Write(b[:n])
+ b = b[n:]
+ }
+ if len(b) > 0 {
+ h.nx = copy(h.x[:], b)
+ }
+}
+
+// HashString hashes the contents of s.
+// It does not explicitly hash the length separately.
+func (h *Block512) HashString(s string) {
+ // TODO: Avoid unsafe when standard hashers implement io.StringWriter.
+ // See https://go.dev/issue/38776.
+ type stringHeader struct {
+ p unsafe.Pointer
+ n int
+ }
+ p := (*stringHeader)(unsafe.Pointer(&s))
+ b := unsafe.Slice((*byte)(p.p), p.n)
+ h.HashBytes(b)
+}
+
+// TODO: Add Hash.MarshalBinary and Hash.UnmarshalBinary?
diff --git a/util/httphdr/httphdr.go b/util/httphdr/httphdr.go index 852e28b8f..b78b165c6 100644 --- a/util/httphdr/httphdr.go +++ b/util/httphdr/httphdr.go @@ -1,197 +1,197 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -// Package httphdr implements functionality for parsing and formatting -// standard HTTP headers. -package httphdr - -import ( - "bytes" - "strconv" - "strings" -) - -// Range is a range of bytes within some content. -type Range struct { - // Start is the starting offset. - // It is zero if Length is negative; it must not be negative. - Start int64 - // Length is the length of the content. - // It is zero if the length extends to the end of the content. - // It is negative if the length is relative to the end (e.g., last 5 bytes). - Length int64 -} - -// ows is optional whitespace. -const ows = " \t" // per RFC 7230, section 3.2.3 - -// ParseRange parses a "Range" header per RFC 7233, section 3. -// It only handles "Range" headers where the units is "bytes". -// The "Range" header is usually only specified in GET requests. -func ParseRange(hdr string) (ranges []Range, ok bool) { - // Grammar per RFC 7233, appendix D: - // Range = byte-ranges-specifier | other-ranges-specifier - // byte-ranges-specifier = bytes-unit "=" byte-range-set - // bytes-unit = "bytes" - // byte-range-set = - // *("," OWS) - // (byte-range-spec | suffix-byte-range-spec) - // *(OWS "," [OWS ( byte-range-spec | suffix-byte-range-spec )]) - // byte-range-spec = first-byte-pos "-" [last-byte-pos] - // suffix-byte-range-spec = "-" suffix-length - // We do not support other-ranges-specifier. - // All other identifiers are 1*DIGIT. - hdr = strings.Trim(hdr, ows) // per RFC 7230, section 3.2 - units, elems, hasUnits := strings.Cut(hdr, "=") - elems = strings.TrimLeft(elems, ","+ows) - for _, elem := range strings.Split(elems, ",") { - elem = strings.Trim(elem, ows) // per RFC 7230, section 7 - switch { - case strings.HasPrefix(elem, "-"): // i.e., "-" suffix-length - n, ok := parseNumber(strings.TrimPrefix(elem, "-")) - if !ok { - return ranges, false - } - ranges = append(ranges, Range{0, -n}) - case strings.HasSuffix(elem, "-"): // i.e., first-byte-pos "-" - n, ok := parseNumber(strings.TrimSuffix(elem, "-")) - if !ok { - return ranges, false - } - ranges = append(ranges, Range{n, 0}) - default: // i.e., first-byte-pos "-" last-byte-pos - prefix, suffix, hasDash := strings.Cut(elem, "-") - n, ok2 := parseNumber(prefix) - m, ok3 := parseNumber(suffix) - if !hasDash || !ok2 || !ok3 || m < n { - return ranges, false - } - ranges = append(ranges, Range{n, m - n + 1}) - } - } - return ranges, units == "bytes" && hasUnits && len(ranges) > 0 // must see at least one element per RFC 7233, section 2.1 -} - -// FormatRange formats a "Range" header per RFC 7233, section 3. -// It only handles "Range" headers where the units is "bytes". -// The "Range" header is usually only specified in GET requests. -func FormatRange(ranges []Range) (hdr string, ok bool) { - b := []byte("bytes=") - for _, r := range ranges { - switch { - case r.Length > 0: // i.e., first-byte-pos "-" last-byte-pos - if r.Start < 0 { - return string(b), false - } - b = strconv.AppendUint(b, uint64(r.Start), 10) - b = append(b, '-') - b = strconv.AppendUint(b, uint64(r.Start+r.Length-1), 10) - b = append(b, ',') - case r.Length == 0: // i.e., first-byte-pos "-" - if r.Start < 0 { - return string(b), false - } - b = strconv.AppendUint(b, uint64(r.Start), 10) - b = append(b, '-') - b = append(b, ',') - case r.Length < 0: // i.e., "-" suffix-length - if r.Start != 0 { - return string(b), false - } - b = append(b, '-') - b = strconv.AppendUint(b, uint64(-r.Length), 10) - b = append(b, ',') - default: - return string(b), false - } - } - return string(bytes.TrimRight(b, ",")), len(ranges) > 0 -} - -// ParseContentRange parses a "Content-Range" header per RFC 7233, section 4.2. -// It only handles "Content-Range" headers where the units is "bytes". -// The "Content-Range" header is usually only specified in HTTP responses. -// -// If only the completeLength is specified, then start and length are both zero. -// -// Otherwise, the parses the start and length and the optional completeLength, -// which is -1 if unspecified. The start is non-negative and the length is positive. -func ParseContentRange(hdr string) (start, length, completeLength int64, ok bool) { - // Grammar per RFC 7233, appendix D: - // Content-Range = byte-content-range | other-content-range - // byte-content-range = bytes-unit SP (byte-range-resp | unsatisfied-range) - // bytes-unit = "bytes" - // byte-range-resp = byte-range "/" (complete-length | "*") - // unsatisfied-range = "*/" complete-length - // byte-range = first-byte-pos "-" last-byte-pos - // We do not support other-content-range. - // All other identifiers are 1*DIGIT. - hdr = strings.Trim(hdr, ows) // per RFC 7230, section 3.2 - suffix, hasUnits := strings.CutPrefix(hdr, "bytes ") - suffix, unsatisfied := strings.CutPrefix(suffix, "*/") - if unsatisfied { // i.e., unsatisfied-range - n, ok := parseNumber(suffix) - if !ok { - return start, length, completeLength, false - } - completeLength = n - } else { // i.e., byte-range "/" (complete-length | "*") - prefix, suffix, hasDash := strings.Cut(suffix, "-") - middle, suffix, hasSlash := strings.Cut(suffix, "/") - n, ok0 := parseNumber(prefix) - m, ok1 := parseNumber(middle) - o, ok2 := parseNumber(suffix) - if suffix == "*" { - o, ok2 = -1, true - } - if !hasDash || !hasSlash || !ok0 || !ok1 || !ok2 || m < n || (o >= 0 && o <= m) { - return start, length, completeLength, false - } - start = n - length = m - n + 1 - completeLength = o - } - return start, length, completeLength, hasUnits -} - -// FormatContentRange parses a "Content-Range" header per RFC 7233, section 4.2. -// It only handles "Content-Range" headers where the units is "bytes". -// The "Content-Range" header is usually only specified in HTTP responses. -// -// If start and length are non-positive, then it encodes just the completeLength, -// which must be a non-negative value. -// -// Otherwise, it encodes the start and length as a byte-range, -// and optionally emits the complete length if it is non-negative. -// The length must be positive (as RFC 7233 uses inclusive end offsets). -func FormatContentRange(start, length, completeLength int64) (hdr string, ok bool) { - b := []byte("bytes ") - switch { - case start <= 0 && length <= 0 && completeLength >= 0: // i.e., unsatisfied-range - b = append(b, "*/"...) - b = strconv.AppendUint(b, uint64(completeLength), 10) - ok = true - case start >= 0 && length > 0: // i.e., byte-range "/" (complete-length | "*") - b = strconv.AppendUint(b, uint64(start), 10) - b = append(b, '-') - b = strconv.AppendUint(b, uint64(start+length-1), 10) - b = append(b, '/') - if completeLength >= 0 { - b = strconv.AppendUint(b, uint64(completeLength), 10) - ok = completeLength >= start+length && start+length > 0 - } else { - b = append(b, '*') - ok = true - } - } - return string(b), ok -} - -// parseNumber parses s as an unsigned decimal integer. -// It parses according to the 1*DIGIT grammar, which allows leading zeros. -func parseNumber(s string) (int64, bool) { - suffix := strings.TrimLeft(s, "0123456789") - prefix := s[:len(s)-len(suffix)] - n, err := strconv.ParseInt(prefix, 10, 64) - return n, suffix == "" && err == nil -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package httphdr implements functionality for parsing and formatting
+// standard HTTP headers.
+package httphdr
+
+import (
+ "bytes"
+ "strconv"
+ "strings"
+)
+
+// Range is a range of bytes within some content.
+type Range struct {
+ // Start is the starting offset.
+ // It is zero if Length is negative; it must not be negative.
+ Start int64
+ // Length is the length of the content.
+ // It is zero if the length extends to the end of the content.
+ // It is negative if the length is relative to the end (e.g., last 5 bytes).
+ Length int64
+}
+
+// ows is optional whitespace.
+const ows = " \t" // per RFC 7230, section 3.2.3
+
+// ParseRange parses a "Range" header per RFC 7233, section 3.
+// It only handles "Range" headers where the units is "bytes".
+// The "Range" header is usually only specified in GET requests.
+func ParseRange(hdr string) (ranges []Range, ok bool) {
+ // Grammar per RFC 7233, appendix D:
+ // Range = byte-ranges-specifier | other-ranges-specifier
+ // byte-ranges-specifier = bytes-unit "=" byte-range-set
+ // bytes-unit = "bytes"
+ // byte-range-set =
+ // *("," OWS)
+ // (byte-range-spec | suffix-byte-range-spec)
+ // *(OWS "," [OWS ( byte-range-spec | suffix-byte-range-spec )])
+ // byte-range-spec = first-byte-pos "-" [last-byte-pos]
+ // suffix-byte-range-spec = "-" suffix-length
+ // We do not support other-ranges-specifier.
+ // All other identifiers are 1*DIGIT.
+ hdr = strings.Trim(hdr, ows) // per RFC 7230, section 3.2
+ units, elems, hasUnits := strings.Cut(hdr, "=")
+ elems = strings.TrimLeft(elems, ","+ows)
+ for _, elem := range strings.Split(elems, ",") {
+ elem = strings.Trim(elem, ows) // per RFC 7230, section 7
+ switch {
+ case strings.HasPrefix(elem, "-"): // i.e., "-" suffix-length
+ n, ok := parseNumber(strings.TrimPrefix(elem, "-"))
+ if !ok {
+ return ranges, false
+ }
+ ranges = append(ranges, Range{0, -n})
+ case strings.HasSuffix(elem, "-"): // i.e., first-byte-pos "-"
+ n, ok := parseNumber(strings.TrimSuffix(elem, "-"))
+ if !ok {
+ return ranges, false
+ }
+ ranges = append(ranges, Range{n, 0})
+ default: // i.e., first-byte-pos "-" last-byte-pos
+ prefix, suffix, hasDash := strings.Cut(elem, "-")
+ n, ok2 := parseNumber(prefix)
+ m, ok3 := parseNumber(suffix)
+ if !hasDash || !ok2 || !ok3 || m < n {
+ return ranges, false
+ }
+ ranges = append(ranges, Range{n, m - n + 1})
+ }
+ }
+ return ranges, units == "bytes" && hasUnits && len(ranges) > 0 // must see at least one element per RFC 7233, section 2.1
+}
+
+// FormatRange formats a "Range" header per RFC 7233, section 3.
+// It only handles "Range" headers where the units is "bytes".
+// The "Range" header is usually only specified in GET requests.
+func FormatRange(ranges []Range) (hdr string, ok bool) {
+ b := []byte("bytes=")
+ for _, r := range ranges {
+ switch {
+ case r.Length > 0: // i.e., first-byte-pos "-" last-byte-pos
+ if r.Start < 0 {
+ return string(b), false
+ }
+ b = strconv.AppendUint(b, uint64(r.Start), 10)
+ b = append(b, '-')
+ b = strconv.AppendUint(b, uint64(r.Start+r.Length-1), 10)
+ b = append(b, ',')
+ case r.Length == 0: // i.e., first-byte-pos "-"
+ if r.Start < 0 {
+ return string(b), false
+ }
+ b = strconv.AppendUint(b, uint64(r.Start), 10)
+ b = append(b, '-')
+ b = append(b, ',')
+ case r.Length < 0: // i.e., "-" suffix-length
+ if r.Start != 0 {
+ return string(b), false
+ }
+ b = append(b, '-')
+ b = strconv.AppendUint(b, uint64(-r.Length), 10)
+ b = append(b, ',')
+ default:
+ return string(b), false
+ }
+ }
+ return string(bytes.TrimRight(b, ",")), len(ranges) > 0
+}
+
+// ParseContentRange parses a "Content-Range" header per RFC 7233, section 4.2.
+// It only handles "Content-Range" headers where the units is "bytes".
+// The "Content-Range" header is usually only specified in HTTP responses.
+//
+// If only the completeLength is specified, then start and length are both zero.
+//
+// Otherwise, the parses the start and length and the optional completeLength,
+// which is -1 if unspecified. The start is non-negative and the length is positive.
+func ParseContentRange(hdr string) (start, length, completeLength int64, ok bool) {
+ // Grammar per RFC 7233, appendix D:
+ // Content-Range = byte-content-range | other-content-range
+ // byte-content-range = bytes-unit SP (byte-range-resp | unsatisfied-range)
+ // bytes-unit = "bytes"
+ // byte-range-resp = byte-range "/" (complete-length | "*")
+ // unsatisfied-range = "*/" complete-length
+ // byte-range = first-byte-pos "-" last-byte-pos
+ // We do not support other-content-range.
+ // All other identifiers are 1*DIGIT.
+ hdr = strings.Trim(hdr, ows) // per RFC 7230, section 3.2
+ suffix, hasUnits := strings.CutPrefix(hdr, "bytes ")
+ suffix, unsatisfied := strings.CutPrefix(suffix, "*/")
+ if unsatisfied { // i.e., unsatisfied-range
+ n, ok := parseNumber(suffix)
+ if !ok {
+ return start, length, completeLength, false
+ }
+ completeLength = n
+ } else { // i.e., byte-range "/" (complete-length | "*")
+ prefix, suffix, hasDash := strings.Cut(suffix, "-")
+ middle, suffix, hasSlash := strings.Cut(suffix, "/")
+ n, ok0 := parseNumber(prefix)
+ m, ok1 := parseNumber(middle)
+ o, ok2 := parseNumber(suffix)
+ if suffix == "*" {
+ o, ok2 = -1, true
+ }
+ if !hasDash || !hasSlash || !ok0 || !ok1 || !ok2 || m < n || (o >= 0 && o <= m) {
+ return start, length, completeLength, false
+ }
+ start = n
+ length = m - n + 1
+ completeLength = o
+ }
+ return start, length, completeLength, hasUnits
+}
+
+// FormatContentRange parses a "Content-Range" header per RFC 7233, section 4.2.
+// It only handles "Content-Range" headers where the units is "bytes".
+// The "Content-Range" header is usually only specified in HTTP responses.
+//
+// If start and length are non-positive, then it encodes just the completeLength,
+// which must be a non-negative value.
+//
+// Otherwise, it encodes the start and length as a byte-range,
+// and optionally emits the complete length if it is non-negative.
+// The length must be positive (as RFC 7233 uses inclusive end offsets).
+func FormatContentRange(start, length, completeLength int64) (hdr string, ok bool) {
+ b := []byte("bytes ")
+ switch {
+ case start <= 0 && length <= 0 && completeLength >= 0: // i.e., unsatisfied-range
+ b = append(b, "*/"...)
+ b = strconv.AppendUint(b, uint64(completeLength), 10)
+ ok = true
+ case start >= 0 && length > 0: // i.e., byte-range "/" (complete-length | "*")
+ b = strconv.AppendUint(b, uint64(start), 10)
+ b = append(b, '-')
+ b = strconv.AppendUint(b, uint64(start+length-1), 10)
+ b = append(b, '/')
+ if completeLength >= 0 {
+ b = strconv.AppendUint(b, uint64(completeLength), 10)
+ ok = completeLength >= start+length && start+length > 0
+ } else {
+ b = append(b, '*')
+ ok = true
+ }
+ }
+ return string(b), ok
+}
+
+// parseNumber parses s as an unsigned decimal integer.
+// It parses according to the 1*DIGIT grammar, which allows leading zeros.
+func parseNumber(s string) (int64, bool) {
+ suffix := strings.TrimLeft(s, "0123456789")
+ prefix := s[:len(s)-len(suffix)]
+ n, err := strconv.ParseInt(prefix, 10, 64)
+ return n, suffix == "" && err == nil
+}
diff --git a/util/httphdr/httphdr_test.go b/util/httphdr/httphdr_test.go index 81feeaca0..77ec0c324 100644 --- a/util/httphdr/httphdr_test.go +++ b/util/httphdr/httphdr_test.go @@ -1,96 +1,96 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package httphdr - -import ( - "testing" - - "github.com/google/go-cmp/cmp" -) - -func valOk[T any](v T, ok bool) (out struct { - V T - Ok bool -}) { - out.V = v - out.Ok = ok - return out -} - -func TestRange(t *testing.T) { - tests := []struct { - in string - want []Range - wantOk bool - roundtrip bool - }{ - {"", nil, false, false}, - {"1-3", nil, false, false}, - {"units=1-3", []Range{{1, 3}}, false, false}, - {"bytes=1-3", []Range{{1, 3}}, true, true}, - {"bytes=#-3", nil, false, false}, - {"bytes=#-", nil, false, false}, - {"bytes=13", nil, false, false}, - {"bytes=1-#", nil, false, false}, - {"bytes=-#", nil, false, false}, - {"bytes= , , , ,\t , \t 1-3", []Range{{1, 3}}, true, false}, - {"bytes=1-1", []Range{{1, 1}}, true, true}, - {"bytes=01-01", []Range{{1, 1}}, true, false}, - {"bytes=1-0", nil, false, false}, - {"bytes=0-5,2-3", []Range{{0, 6}, {2, 2}}, true, true}, - {"bytes=2-3,0-5", []Range{{2, 2}, {0, 6}}, true, true}, - {"bytes=0-5,2-,-5", []Range{{0, 6}, {2, 0}, {0, -5}}, true, true}, - } - - for _, tt := range tests { - got, gotOk := ParseRange(tt.in) - if d := cmp.Diff(valOk(got, gotOk), valOk(tt.want, tt.wantOk)); d != "" { - t.Errorf("ParseRange(%q) mismatch (-got +want):\n%s", tt.in, d) - } - if tt.roundtrip { - got, gotOk := FormatRange(tt.want) - if d := cmp.Diff(valOk(got, gotOk), valOk(tt.in, tt.wantOk)); d != "" { - t.Errorf("FormatRange(%v) mismatch (-got +want):\n%s", tt.want, d) - } - } - } -} - -type contentRange struct{ Start, Length, CompleteLength int64 } - -func TestContentRange(t *testing.T) { - tests := []struct { - in string - want contentRange - wantOk bool - roundtrip bool - }{ - {"", contentRange{}, false, false}, - {"bytes 5-6/*", contentRange{5, 2, -1}, true, true}, - {"units 5-6/*", contentRange{}, false, false}, - {"bytes 5-6/*", contentRange{}, false, false}, - {"bytes 5-5/*", contentRange{5, 1, -1}, true, true}, - {"bytes 5-4/*", contentRange{}, false, false}, - {"bytes 5-5/6", contentRange{5, 1, 6}, true, true}, - {"bytes 05-005/0006", contentRange{5, 1, 6}, true, false}, - {"bytes 5-5/5", contentRange{}, false, false}, - {"bytes #-5/6", contentRange{}, false, false}, - {"bytes 5-#/6", contentRange{}, false, false}, - {"bytes 5-5/#", contentRange{}, false, false}, - } - - for _, tt := range tests { - start, length, completeLength, gotOk := ParseContentRange(tt.in) - got := contentRange{start, length, completeLength} - if d := cmp.Diff(valOk(got, gotOk), valOk(tt.want, tt.wantOk)); d != "" { - t.Errorf("ParseContentRange mismatch (-got +want):\n%s", d) - } - if tt.roundtrip { - got, gotOk := FormatContentRange(tt.want.Start, tt.want.Length, tt.want.CompleteLength) - if d := cmp.Diff(valOk(got, gotOk), valOk(tt.in, tt.wantOk)); d != "" { - t.Errorf("FormatContentRange mismatch (-got +want):\n%s", d) - } - } - } -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package httphdr
+
+import (
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+)
+
+func valOk[T any](v T, ok bool) (out struct {
+ V T
+ Ok bool
+}) {
+ out.V = v
+ out.Ok = ok
+ return out
+}
+
+func TestRange(t *testing.T) {
+ tests := []struct {
+ in string
+ want []Range
+ wantOk bool
+ roundtrip bool
+ }{
+ {"", nil, false, false},
+ {"1-3", nil, false, false},
+ {"units=1-3", []Range{{1, 3}}, false, false},
+ {"bytes=1-3", []Range{{1, 3}}, true, true},
+ {"bytes=#-3", nil, false, false},
+ {"bytes=#-", nil, false, false},
+ {"bytes=13", nil, false, false},
+ {"bytes=1-#", nil, false, false},
+ {"bytes=-#", nil, false, false},
+ {"bytes= , , , ,\t , \t 1-3", []Range{{1, 3}}, true, false},
+ {"bytes=1-1", []Range{{1, 1}}, true, true},
+ {"bytes=01-01", []Range{{1, 1}}, true, false},
+ {"bytes=1-0", nil, false, false},
+ {"bytes=0-5,2-3", []Range{{0, 6}, {2, 2}}, true, true},
+ {"bytes=2-3,0-5", []Range{{2, 2}, {0, 6}}, true, true},
+ {"bytes=0-5,2-,-5", []Range{{0, 6}, {2, 0}, {0, -5}}, true, true},
+ }
+
+ for _, tt := range tests {
+ got, gotOk := ParseRange(tt.in)
+ if d := cmp.Diff(valOk(got, gotOk), valOk(tt.want, tt.wantOk)); d != "" {
+ t.Errorf("ParseRange(%q) mismatch (-got +want):\n%s", tt.in, d)
+ }
+ if tt.roundtrip {
+ got, gotOk := FormatRange(tt.want)
+ if d := cmp.Diff(valOk(got, gotOk), valOk(tt.in, tt.wantOk)); d != "" {
+ t.Errorf("FormatRange(%v) mismatch (-got +want):\n%s", tt.want, d)
+ }
+ }
+ }
+}
+
+type contentRange struct{ Start, Length, CompleteLength int64 }
+
+func TestContentRange(t *testing.T) {
+ tests := []struct {
+ in string
+ want contentRange
+ wantOk bool
+ roundtrip bool
+ }{
+ {"", contentRange{}, false, false},
+ {"bytes 5-6/*", contentRange{5, 2, -1}, true, true},
+ {"units 5-6/*", contentRange{}, false, false},
+ {"bytes 5-6/*", contentRange{}, false, false},
+ {"bytes 5-5/*", contentRange{5, 1, -1}, true, true},
+ {"bytes 5-4/*", contentRange{}, false, false},
+ {"bytes 5-5/6", contentRange{5, 1, 6}, true, true},
+ {"bytes 05-005/0006", contentRange{5, 1, 6}, true, false},
+ {"bytes 5-5/5", contentRange{}, false, false},
+ {"bytes #-5/6", contentRange{}, false, false},
+ {"bytes 5-#/6", contentRange{}, false, false},
+ {"bytes 5-5/#", contentRange{}, false, false},
+ }
+
+ for _, tt := range tests {
+ start, length, completeLength, gotOk := ParseContentRange(tt.in)
+ got := contentRange{start, length, completeLength}
+ if d := cmp.Diff(valOk(got, gotOk), valOk(tt.want, tt.wantOk)); d != "" {
+ t.Errorf("ParseContentRange mismatch (-got +want):\n%s", d)
+ }
+ if tt.roundtrip {
+ got, gotOk := FormatContentRange(tt.want.Start, tt.want.Length, tt.want.CompleteLength)
+ if d := cmp.Diff(valOk(got, gotOk), valOk(tt.in, tt.wantOk)); d != "" {
+ t.Errorf("FormatContentRange mismatch (-got +want):\n%s", d)
+ }
+ }
+ }
+}
diff --git a/util/httpm/httpm.go b/util/httpm/httpm.go index a9a691b8a..05292f0fa 100644 --- a/util/httpm/httpm.go +++ b/util/httpm/httpm.go @@ -1,36 +1,36 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -// Package httpm has shorter names for HTTP method constants. -// -// Some background: originally Go didn't have http.MethodGet, http.MethodPost -// and life was good and people just wrote readable "GET" and "POST". But then -// in a moment of weakness Brad and others maintaining net/http caved and let -// the http.MethodFoo constants be added and code's been less readable since. -// Now the substance of the method name is hidden away at the end after -// "http.Method" and they all blend together and it's hard to read code using -// them. -// -// This package is a compromise. It provides constants, but shorter and closer -// to how it used to look. It does violate Go style -// (https://github.com/golang/go/wiki/CodeReviewComments#mixed-caps) that says -// constants shouldn't be SCREAM_CASE. But this isn't INT_MAX; it's GET and -// POST, which are already defined as all caps. -// -// It would be tempting to make these constants be typed but then they wouldn't -// be assignable to things in net/http that just want string. Oh well. -package httpm - -const ( - GET = "GET" - HEAD = "HEAD" - POST = "POST" - PUT = "PUT" - PATCH = "PATCH" - DELETE = "DELETE" - CONNECT = "CONNECT" - OPTIONS = "OPTIONS" - TRACE = "TRACE" - SPACEJUMP = "SPACEJUMP" // https://www.w3.org/Protocols/HTTP/Methods/SpaceJump.html - BREW = "BREW" // https://datatracker.ietf.org/doc/html/rfc2324#section-2.1.1 -) +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package httpm has shorter names for HTTP method constants.
+//
+// Some background: originally Go didn't have http.MethodGet, http.MethodPost
+// and life was good and people just wrote readable "GET" and "POST". But then
+// in a moment of weakness Brad and others maintaining net/http caved and let
+// the http.MethodFoo constants be added and code's been less readable since.
+// Now the substance of the method name is hidden away at the end after
+// "http.Method" and they all blend together and it's hard to read code using
+// them.
+//
+// This package is a compromise. It provides constants, but shorter and closer
+// to how it used to look. It does violate Go style
+// (https://github.com/golang/go/wiki/CodeReviewComments#mixed-caps) that says
+// constants shouldn't be SCREAM_CASE. But this isn't INT_MAX; it's GET and
+// POST, which are already defined as all caps.
+//
+// It would be tempting to make these constants be typed but then they wouldn't
+// be assignable to things in net/http that just want string. Oh well.
+package httpm
+
+const (
+ GET = "GET"
+ HEAD = "HEAD"
+ POST = "POST"
+ PUT = "PUT"
+ PATCH = "PATCH"
+ DELETE = "DELETE"
+ CONNECT = "CONNECT"
+ OPTIONS = "OPTIONS"
+ TRACE = "TRACE"
+ SPACEJUMP = "SPACEJUMP" // https://www.w3.org/Protocols/HTTP/Methods/SpaceJump.html
+ BREW = "BREW" // https://datatracker.ietf.org/doc/html/rfc2324#section-2.1.1
+)
diff --git a/util/httpm/httpm_test.go b/util/httpm/httpm_test.go index 0c71edc2f..cbe327d95 100644 --- a/util/httpm/httpm_test.go +++ b/util/httpm/httpm_test.go @@ -1,37 +1,37 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package httpm - -import ( - "os" - "os/exec" - "path/filepath" - "strings" - "testing" -) - -func TestUsedConsistently(t *testing.T) { - dir, err := os.Getwd() - if err != nil { - t.Fatal(err) - } - rootDir := filepath.Join(dir, "../..") - - // If we don't have a .git directory, we're not in a git checkout (e.g. - // a downstream package); skip this test. - if _, err := os.Stat(filepath.Join(rootDir, ".git")); err != nil { - t.Skipf("skipping test since .git doesn't exist: %v", err) - } - - cmd := exec.Command("git", "grep", "-l", "-F", "http.Method") - cmd.Dir = rootDir - matches, _ := cmd.Output() - for _, fn := range strings.Split(strings.TrimSpace(string(matches)), "\n") { - switch fn { - case "util/httpm/httpm.go", "util/httpm/httpm_test.go": - continue - } - t.Errorf("http.MethodFoo constant used in %s; use httpm.FOO instead", fn) - } -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package httpm
+
+import (
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+ "testing"
+)
+
+func TestUsedConsistently(t *testing.T) {
+ dir, err := os.Getwd()
+ if err != nil {
+ t.Fatal(err)
+ }
+ rootDir := filepath.Join(dir, "../..")
+
+ // If we don't have a .git directory, we're not in a git checkout (e.g.
+ // a downstream package); skip this test.
+ if _, err := os.Stat(filepath.Join(rootDir, ".git")); err != nil {
+ t.Skipf("skipping test since .git doesn't exist: %v", err)
+ }
+
+ cmd := exec.Command("git", "grep", "-l", "-F", "http.Method")
+ cmd.Dir = rootDir
+ matches, _ := cmd.Output()
+ for _, fn := range strings.Split(strings.TrimSpace(string(matches)), "\n") {
+ switch fn {
+ case "util/httpm/httpm.go", "util/httpm/httpm_test.go":
+ continue
+ }
+ t.Errorf("http.MethodFoo constant used in %s; use httpm.FOO instead", fn)
+ }
+}
diff --git a/util/jsonutil/types.go b/util/jsonutil/types.go index 057473249..2ee53f44a 100644 --- a/util/jsonutil/types.go +++ b/util/jsonutil/types.go @@ -1,16 +1,16 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package jsonutil - -// Bytes is a byte slice in a json-encoded struct. -// encoding/json assumes that []byte fields are hex-encoded. -// Bytes are not hex-encoded; they are treated the same as strings. -// This can avoid unnecessary allocations due to a round trip through strings. -type Bytes []byte - -func (b *Bytes) UnmarshalText(text []byte) error { - // Copy the contexts of text. - *b = append(*b, text...) - return nil -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package jsonutil
+
+// Bytes is a byte slice in a json-encoded struct.
+// encoding/json assumes that []byte fields are hex-encoded.
+// Bytes are not hex-encoded; they are treated the same as strings.
+// This can avoid unnecessary allocations due to a round trip through strings.
+type Bytes []byte
+
+func (b *Bytes) UnmarshalText(text []byte) error {
+ // Copy the contexts of text.
+ *b = append(*b, text...)
+ return nil
+}
diff --git a/util/jsonutil/unmarshal.go b/util/jsonutil/unmarshal.go index b1eb4ea87..13aea0c87 100644 --- a/util/jsonutil/unmarshal.go +++ b/util/jsonutil/unmarshal.go @@ -1,89 +1,89 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -// Package jsonutil provides utilities to improve JSON performance. -// It includes an Unmarshal wrapper that amortizes allocated garbage over subsequent runs -// and a Bytes type to reduce allocations when unmarshalling a non-hex-encoded string into a []byte. -package jsonutil - -import ( - "bytes" - "encoding/json" - "sync" -) - -// decoder is a re-usable json decoder. -type decoder struct { - dec *json.Decoder - r *bytes.Reader -} - -var readerPool = sync.Pool{ - New: func() any { - return bytes.NewReader(nil) - }, -} - -var decoderPool = sync.Pool{ - New: func() any { - var d decoder - d.r = readerPool.Get().(*bytes.Reader) - d.dec = json.NewDecoder(d.r) - return &d - }, -} - -// Unmarshal is similar to encoding/json.Unmarshal. -// There are three major differences: -// -// On error, encoding/json.Unmarshal zeros v. -// This Unmarshal may leave partial data in v. -// Always check the error before using v! -// (Future improvements may remove this bug.) -// -// The errors they return don't always match perfectly. -// If you do error matching more precise than err != nil, -// don't use this Unmarshal. -// -// This Unmarshal allocates considerably less memory. -func Unmarshal(b []byte, v any) error { - d := decoderPool.Get().(*decoder) - d.r.Reset(b) - off := d.dec.InputOffset() - err := d.dec.Decode(v) - d.r.Reset(nil) // don't keep a reference to b - // In case of error, report the offset in this byte slice, - // instead of in the totality of all bytes this decoder has processed. - // It is not possible to make all errors match json.Unmarshal exactly, - // but we can at least try. - switch jsonerr := err.(type) { - case *json.SyntaxError: - jsonerr.Offset -= off - case *json.UnmarshalTypeError: - jsonerr.Offset -= off - case nil: - // json.Unmarshal fails if there's any extra junk in the input. - // json.Decoder does not; see https://github.com/golang/go/issues/36225. - // We need to check for anything left over in the buffer. - if d.dec.More() { - // TODO: Provide a better error message. - // Unfortunately, we can't set the msg field. - // The offset doesn't perfectly match json: - // Ours is at the end of the valid data, - // and theirs is at the beginning of the extra data after whitespace. - // Close enough, though. - err = &json.SyntaxError{Offset: d.dec.InputOffset() - off} - - // TODO: zero v. This is hard; see encoding/json.indirect. - } - } - if err == nil { - decoderPool.Put(d) - } else { - // There might be junk left in the decoder's buffer. - // There's no way to flush it, no Reset method. - // Abandoned the decoder but reuse the reader. - readerPool.Put(d.r) - } - return err -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package jsonutil provides utilities to improve JSON performance.
+// It includes an Unmarshal wrapper that amortizes allocated garbage over subsequent runs
+// and a Bytes type to reduce allocations when unmarshalling a non-hex-encoded string into a []byte.
+package jsonutil
+
+import (
+ "bytes"
+ "encoding/json"
+ "sync"
+)
+
+// decoder is a re-usable json decoder.
+type decoder struct {
+ dec *json.Decoder
+ r *bytes.Reader
+}
+
+var readerPool = sync.Pool{
+ New: func() any {
+ return bytes.NewReader(nil)
+ },
+}
+
+var decoderPool = sync.Pool{
+ New: func() any {
+ var d decoder
+ d.r = readerPool.Get().(*bytes.Reader)
+ d.dec = json.NewDecoder(d.r)
+ return &d
+ },
+}
+
+// Unmarshal is similar to encoding/json.Unmarshal.
+// There are three major differences:
+//
+// On error, encoding/json.Unmarshal zeros v.
+// This Unmarshal may leave partial data in v.
+// Always check the error before using v!
+// (Future improvements may remove this bug.)
+//
+// The errors they return don't always match perfectly.
+// If you do error matching more precise than err != nil,
+// don't use this Unmarshal.
+//
+// This Unmarshal allocates considerably less memory.
+func Unmarshal(b []byte, v any) error {
+ d := decoderPool.Get().(*decoder)
+ d.r.Reset(b)
+ off := d.dec.InputOffset()
+ err := d.dec.Decode(v)
+ d.r.Reset(nil) // don't keep a reference to b
+ // In case of error, report the offset in this byte slice,
+ // instead of in the totality of all bytes this decoder has processed.
+ // It is not possible to make all errors match json.Unmarshal exactly,
+ // but we can at least try.
+ switch jsonerr := err.(type) {
+ case *json.SyntaxError:
+ jsonerr.Offset -= off
+ case *json.UnmarshalTypeError:
+ jsonerr.Offset -= off
+ case nil:
+ // json.Unmarshal fails if there's any extra junk in the input.
+ // json.Decoder does not; see https://github.com/golang/go/issues/36225.
+ // We need to check for anything left over in the buffer.
+ if d.dec.More() {
+ // TODO: Provide a better error message.
+ // Unfortunately, we can't set the msg field.
+ // The offset doesn't perfectly match json:
+ // Ours is at the end of the valid data,
+ // and theirs is at the beginning of the extra data after whitespace.
+ // Close enough, though.
+ err = &json.SyntaxError{Offset: d.dec.InputOffset() - off}
+
+ // TODO: zero v. This is hard; see encoding/json.indirect.
+ }
+ }
+ if err == nil {
+ decoderPool.Put(d)
+ } else {
+ // There might be junk left in the decoder's buffer.
+ // There's no way to flush it, no Reset method.
+ // Abandoned the decoder but reuse the reader.
+ readerPool.Put(d.r)
+ }
+ return err
+}
diff --git a/util/lineread/lineread.go b/util/lineread/lineread.go index 6b01d2b69..2a7486e0a 100644 --- a/util/lineread/lineread.go +++ b/util/lineread/lineread.go @@ -1,37 +1,37 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -// Package lineread reads lines from files. It's not fancy, but it got repetitive. -package lineread - -import ( - "bufio" - "io" - "os" -) - -// File opens name and calls fn for each line. It returns an error if the Open failed -// or once fn returns an error. -func File(name string, fn func(line []byte) error) error { - f, err := os.Open(name) - if err != nil { - return err - } - defer f.Close() - return Reader(f, fn) -} - -// Reader calls fn for each line. -// If fn returns an error, Reader stops reading and returns that error. -// Reader may also return errors encountered reading and parsing from r. -// To stop reading early, use a sentinel "stop" error value and ignore -// it when returned from Reader. -func Reader(r io.Reader, fn func(line []byte) error) error { - bs := bufio.NewScanner(r) - for bs.Scan() { - if err := fn(bs.Bytes()); err != nil { - return err - } - } - return bs.Err() -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package lineread reads lines from files. It's not fancy, but it got repetitive.
+package lineread
+
+import (
+ "bufio"
+ "io"
+ "os"
+)
+
+// File opens name and calls fn for each line. It returns an error if the Open failed
+// or once fn returns an error.
+func File(name string, fn func(line []byte) error) error {
+ f, err := os.Open(name)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+ return Reader(f, fn)
+}
+
+// Reader calls fn for each line.
+// If fn returns an error, Reader stops reading and returns that error.
+// Reader may also return errors encountered reading and parsing from r.
+// To stop reading early, use a sentinel "stop" error value and ignore
+// it when returned from Reader.
+func Reader(r io.Reader, fn func(line []byte) error) error {
+ bs := bufio.NewScanner(r)
+ for bs.Scan() {
+ if err := fn(bs.Bytes()); err != nil {
+ return err
+ }
+ }
+ return bs.Err()
+}
diff --git a/util/linuxfw/linuxfwtest/linuxfwtest.go b/util/linuxfw/linuxfwtest/linuxfwtest.go index ee2cbd1b2..04f179199 100644 --- a/util/linuxfw/linuxfwtest/linuxfwtest.go +++ b/util/linuxfw/linuxfwtest/linuxfwtest.go @@ -1,31 +1,31 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -//go:build cgo && linux - -// Package linuxfwtest contains tests for the linuxfw package. Go does not -// support cgo in tests, and we don't want the main package to have a cgo -// dependency, so we put all the tests here and call them from the main package -// in tests intead. -package linuxfwtest - -import ( - "testing" - "unsafe" -) - -/* -#include <sys/socket.h> // socket() -*/ -import "C" - -type SizeInfo struct { - SizeofSocklen uintptr -} - -func TestSizes(t *testing.T, si *SizeInfo) { - want := unsafe.Sizeof(C.socklen_t(0)) - if want != si.SizeofSocklen { - t.Errorf("sockLen has wrong size; want=%d got=%d", want, si.SizeofSocklen) - } -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build cgo && linux
+
+// Package linuxfwtest contains tests for the linuxfw package. Go does not
+// support cgo in tests, and we don't want the main package to have a cgo
+// dependency, so we put all the tests here and call them from the main package
+// in tests intead.
+package linuxfwtest
+
+import (
+ "testing"
+ "unsafe"
+)
+
+/*
+#include <sys/socket.h> // socket()
+*/
+import "C"
+
+type SizeInfo struct {
+ SizeofSocklen uintptr
+}
+
+func TestSizes(t *testing.T, si *SizeInfo) {
+ want := unsafe.Sizeof(C.socklen_t(0))
+ if want != si.SizeofSocklen {
+ t.Errorf("sockLen has wrong size; want=%d got=%d", want, si.SizeofSocklen)
+ }
+}
diff --git a/util/linuxfw/linuxfwtest/linuxfwtest_unsupported.go b/util/linuxfw/linuxfwtest/linuxfwtest_unsupported.go index 6e9569900..d5e297da7 100644 --- a/util/linuxfw/linuxfwtest/linuxfwtest_unsupported.go +++ b/util/linuxfw/linuxfwtest/linuxfwtest_unsupported.go @@ -1,18 +1,18 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -//go:build !cgo || !linux - -package linuxfwtest - -import ( - "testing" -) - -type SizeInfo struct { - SizeofSocklen uintptr -} - -func TestSizes(t *testing.T, si *SizeInfo) { - t.Skip("not supported without cgo") -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build !cgo || !linux
+
+package linuxfwtest
+
+import (
+ "testing"
+)
+
+type SizeInfo struct {
+ SizeofSocklen uintptr
+}
+
+func TestSizes(t *testing.T, si *SizeInfo) {
+ t.Skip("not supported without cgo")
+}
diff --git a/util/linuxfw/nftables_types.go b/util/linuxfw/nftables_types.go index b6e24d2a6..a8c5a0730 100644 --- a/util/linuxfw/nftables_types.go +++ b/util/linuxfw/nftables_types.go @@ -1,95 +1,95 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -// TODO(#8502): add support for more architectures -//go:build linux && (arm64 || amd64) - -package linuxfw - -import ( - "github.com/google/nftables/expr" - "github.com/google/nftables/xt" -) - -var metaKeyNames = map[expr.MetaKey]string{ - expr.MetaKeyLEN: "LEN", - expr.MetaKeyPROTOCOL: "PROTOCOL", - expr.MetaKeyPRIORITY: "PRIORITY", - expr.MetaKeyMARK: "MARK", - expr.MetaKeyIIF: "IIF", - expr.MetaKeyOIF: "OIF", - expr.MetaKeyIIFNAME: "IIFNAME", - expr.MetaKeyOIFNAME: "OIFNAME", - expr.MetaKeyIIFTYPE: "IIFTYPE", - expr.MetaKeyOIFTYPE: "OIFTYPE", - expr.MetaKeySKUID: "SKUID", - expr.MetaKeySKGID: "SKGID", - expr.MetaKeyNFTRACE: "NFTRACE", - expr.MetaKeyRTCLASSID: "RTCLASSID", - expr.MetaKeySECMARK: "SECMARK", - expr.MetaKeyNFPROTO: "NFPROTO", - expr.MetaKeyL4PROTO: "L4PROTO", - expr.MetaKeyBRIIIFNAME: "BRIIIFNAME", - expr.MetaKeyBRIOIFNAME: "BRIOIFNAME", - expr.MetaKeyPKTTYPE: "PKTTYPE", - expr.MetaKeyCPU: "CPU", - expr.MetaKeyIIFGROUP: "IIFGROUP", - expr.MetaKeyOIFGROUP: "OIFGROUP", - expr.MetaKeyCGROUP: "CGROUP", - expr.MetaKeyPRANDOM: "PRANDOM", -} - -var cmpOpNames = map[expr.CmpOp]string{ - expr.CmpOpEq: "EQ", - expr.CmpOpNeq: "NEQ", - expr.CmpOpLt: "LT", - expr.CmpOpLte: "LTE", - expr.CmpOpGt: "GT", - expr.CmpOpGte: "GTE", -} - -var verdictNames = map[expr.VerdictKind]string{ - expr.VerdictReturn: "RETURN", - expr.VerdictGoto: "GOTO", - expr.VerdictJump: "JUMP", - expr.VerdictBreak: "BREAK", - expr.VerdictContinue: "CONTINUE", - expr.VerdictDrop: "DROP", - expr.VerdictAccept: "ACCEPT", - expr.VerdictStolen: "STOLEN", - expr.VerdictQueue: "QUEUE", - expr.VerdictRepeat: "REPEAT", - expr.VerdictStop: "STOP", -} - -var payloadOperationTypeNames = map[expr.PayloadOperationType]string{ - expr.PayloadLoad: "LOAD", - expr.PayloadWrite: "WRITE", -} - -var payloadBaseNames = map[expr.PayloadBase]string{ - expr.PayloadBaseLLHeader: "ll-header", - expr.PayloadBaseNetworkHeader: "network-header", - expr.PayloadBaseTransportHeader: "transport-header", -} - -var packetTypeNames = map[int]string{ - 0 /* PACKET_HOST */ : "unicast", - 1 /* PACKET_BROADCAST */ : "broadcast", - 2 /* PACKET_MULTICAST */ : "multicast", -} - -var addrTypeFlagNames = map[xt.AddrTypeFlags]string{ - xt.AddrTypeUnspec: "unspec", - xt.AddrTypeUnicast: "unicast", - xt.AddrTypeLocal: "local", - xt.AddrTypeBroadcast: "broadcast", - xt.AddrTypeAnycast: "anycast", - xt.AddrTypeMulticast: "multicast", - xt.AddrTypeBlackhole: "blackhole", - xt.AddrTypeUnreachable: "unreachable", - xt.AddrTypeProhibit: "prohibit", - xt.AddrTypeThrow: "throw", - xt.AddrTypeNat: "nat", - xt.AddrTypeXresolve: "xresolve", -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// TODO(#8502): add support for more architectures
+//go:build linux && (arm64 || amd64)
+
+package linuxfw
+
+import (
+ "github.com/google/nftables/expr"
+ "github.com/google/nftables/xt"
+)
+
+var metaKeyNames = map[expr.MetaKey]string{
+ expr.MetaKeyLEN: "LEN",
+ expr.MetaKeyPROTOCOL: "PROTOCOL",
+ expr.MetaKeyPRIORITY: "PRIORITY",
+ expr.MetaKeyMARK: "MARK",
+ expr.MetaKeyIIF: "IIF",
+ expr.MetaKeyOIF: "OIF",
+ expr.MetaKeyIIFNAME: "IIFNAME",
+ expr.MetaKeyOIFNAME: "OIFNAME",
+ expr.MetaKeyIIFTYPE: "IIFTYPE",
+ expr.MetaKeyOIFTYPE: "OIFTYPE",
+ expr.MetaKeySKUID: "SKUID",
+ expr.MetaKeySKGID: "SKGID",
+ expr.MetaKeyNFTRACE: "NFTRACE",
+ expr.MetaKeyRTCLASSID: "RTCLASSID",
+ expr.MetaKeySECMARK: "SECMARK",
+ expr.MetaKeyNFPROTO: "NFPROTO",
+ expr.MetaKeyL4PROTO: "L4PROTO",
+ expr.MetaKeyBRIIIFNAME: "BRIIIFNAME",
+ expr.MetaKeyBRIOIFNAME: "BRIOIFNAME",
+ expr.MetaKeyPKTTYPE: "PKTTYPE",
+ expr.MetaKeyCPU: "CPU",
+ expr.MetaKeyIIFGROUP: "IIFGROUP",
+ expr.MetaKeyOIFGROUP: "OIFGROUP",
+ expr.MetaKeyCGROUP: "CGROUP",
+ expr.MetaKeyPRANDOM: "PRANDOM",
+}
+
+var cmpOpNames = map[expr.CmpOp]string{
+ expr.CmpOpEq: "EQ",
+ expr.CmpOpNeq: "NEQ",
+ expr.CmpOpLt: "LT",
+ expr.CmpOpLte: "LTE",
+ expr.CmpOpGt: "GT",
+ expr.CmpOpGte: "GTE",
+}
+
+var verdictNames = map[expr.VerdictKind]string{
+ expr.VerdictReturn: "RETURN",
+ expr.VerdictGoto: "GOTO",
+ expr.VerdictJump: "JUMP",
+ expr.VerdictBreak: "BREAK",
+ expr.VerdictContinue: "CONTINUE",
+ expr.VerdictDrop: "DROP",
+ expr.VerdictAccept: "ACCEPT",
+ expr.VerdictStolen: "STOLEN",
+ expr.VerdictQueue: "QUEUE",
+ expr.VerdictRepeat: "REPEAT",
+ expr.VerdictStop: "STOP",
+}
+
+var payloadOperationTypeNames = map[expr.PayloadOperationType]string{
+ expr.PayloadLoad: "LOAD",
+ expr.PayloadWrite: "WRITE",
+}
+
+var payloadBaseNames = map[expr.PayloadBase]string{
+ expr.PayloadBaseLLHeader: "ll-header",
+ expr.PayloadBaseNetworkHeader: "network-header",
+ expr.PayloadBaseTransportHeader: "transport-header",
+}
+
+var packetTypeNames = map[int]string{
+ 0 /* PACKET_HOST */ : "unicast",
+ 1 /* PACKET_BROADCAST */ : "broadcast",
+ 2 /* PACKET_MULTICAST */ : "multicast",
+}
+
+var addrTypeFlagNames = map[xt.AddrTypeFlags]string{
+ xt.AddrTypeUnspec: "unspec",
+ xt.AddrTypeUnicast: "unicast",
+ xt.AddrTypeLocal: "local",
+ xt.AddrTypeBroadcast: "broadcast",
+ xt.AddrTypeAnycast: "anycast",
+ xt.AddrTypeMulticast: "multicast",
+ xt.AddrTypeBlackhole: "blackhole",
+ xt.AddrTypeUnreachable: "unreachable",
+ xt.AddrTypeProhibit: "prohibit",
+ xt.AddrTypeThrow: "throw",
+ xt.AddrTypeNat: "nat",
+ xt.AddrTypeXresolve: "xresolve",
+}
diff --git a/util/mak/mak.go b/util/mak/mak.go index b421fb0ed..b0d64daa4 100644 --- a/util/mak/mak.go +++ b/util/mak/mak.go @@ -1,70 +1,70 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -// Package mak helps make maps. It contains generic helpers to make/assign -// things, notably to maps, but also slices. -package mak - -import ( - "fmt" - "reflect" -) - -// Set populates an entry in a map, making the map if necessary. -// -// That is, it assigns (*m)[k] = v, making *m if it was nil. -func Set[K comparable, V any, T ~map[K]V](m *T, k K, v V) { - if *m == nil { - *m = make(map[K]V) - } - (*m)[k] = v -} - -// NonNil takes a pointer to a Go data structure -// (currently only a slice or a map) and makes sure it's non-nil for -// JSON serialization. (In particular, JavaScript clients usually want -// the field to be defined after they decode the JSON.) -// -// Deprecated: use NonNilSliceForJSON or NonNilMapForJSON instead. -func NonNil(ptr any) { - if ptr == nil { - panic("nil interface") - } - rv := reflect.ValueOf(ptr) - if rv.Kind() != reflect.Ptr { - panic(fmt.Sprintf("kind %v, not Ptr", rv.Kind())) - } - if rv.Pointer() == 0 { - panic("nil pointer") - } - rv = rv.Elem() - if rv.Pointer() != 0 { - return - } - switch rv.Type().Kind() { - case reflect.Slice: - rv.Set(reflect.MakeSlice(rv.Type(), 0, 0)) - case reflect.Map: - rv.Set(reflect.MakeMap(rv.Type())) - } -} - -// NonNilSliceForJSON makes sure that *slicePtr is non-nil so it will -// won't be omitted from JSON serialization and possibly confuse JavaScript -// clients expecting it to be present. -func NonNilSliceForJSON[T any, S ~[]T](slicePtr *S) { - if *slicePtr != nil { - return - } - *slicePtr = make([]T, 0) -} - -// NonNilMapForJSON makes sure that *slicePtr is non-nil so it will -// won't be omitted from JSON serialization and possibly confuse JavaScript -// clients expecting it to be present. -func NonNilMapForJSON[K comparable, V any, M ~map[K]V](mapPtr *M) { - if *mapPtr != nil { - return - } - *mapPtr = make(M) -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package mak helps make maps. It contains generic helpers to make/assign
+// things, notably to maps, but also slices.
+package mak
+
+import (
+ "fmt"
+ "reflect"
+)
+
+// Set populates an entry in a map, making the map if necessary.
+//
+// That is, it assigns (*m)[k] = v, making *m if it was nil.
+func Set[K comparable, V any, T ~map[K]V](m *T, k K, v V) {
+ if *m == nil {
+ *m = make(map[K]V)
+ }
+ (*m)[k] = v
+}
+
+// NonNil takes a pointer to a Go data structure
+// (currently only a slice or a map) and makes sure it's non-nil for
+// JSON serialization. (In particular, JavaScript clients usually want
+// the field to be defined after they decode the JSON.)
+//
+// Deprecated: use NonNilSliceForJSON or NonNilMapForJSON instead.
+func NonNil(ptr any) {
+ if ptr == nil {
+ panic("nil interface")
+ }
+ rv := reflect.ValueOf(ptr)
+ if rv.Kind() != reflect.Ptr {
+ panic(fmt.Sprintf("kind %v, not Ptr", rv.Kind()))
+ }
+ if rv.Pointer() == 0 {
+ panic("nil pointer")
+ }
+ rv = rv.Elem()
+ if rv.Pointer() != 0 {
+ return
+ }
+ switch rv.Type().Kind() {
+ case reflect.Slice:
+ rv.Set(reflect.MakeSlice(rv.Type(), 0, 0))
+ case reflect.Map:
+ rv.Set(reflect.MakeMap(rv.Type()))
+ }
+}
+
+// NonNilSliceForJSON makes sure that *slicePtr is non-nil so it will
+// won't be omitted from JSON serialization and possibly confuse JavaScript
+// clients expecting it to be present.
+func NonNilSliceForJSON[T any, S ~[]T](slicePtr *S) {
+ if *slicePtr != nil {
+ return
+ }
+ *slicePtr = make([]T, 0)
+}
+
+// NonNilMapForJSON makes sure that *slicePtr is non-nil so it will
+// won't be omitted from JSON serialization and possibly confuse JavaScript
+// clients expecting it to be present.
+func NonNilMapForJSON[K comparable, V any, M ~map[K]V](mapPtr *M) {
+ if *mapPtr != nil {
+ return
+ }
+ *mapPtr = make(M)
+}
diff --git a/util/mak/mak_test.go b/util/mak/mak_test.go index 4de499a9d..dc1d7e93d 100644 --- a/util/mak/mak_test.go +++ b/util/mak/mak_test.go @@ -1,88 +1,88 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -// Package mak contains code to help make things. -package mak - -import ( - "reflect" - "testing" -) - -type M map[string]int - -func TestSet(t *testing.T) { - t.Run("unnamed", func(t *testing.T) { - var m map[string]int - Set(&m, "foo", 42) - Set(&m, "bar", 1) - Set(&m, "bar", 2) - want := map[string]int{ - "foo": 42, - "bar": 2, - } - if got := m; !reflect.DeepEqual(got, want) { - t.Errorf("got %v; want %v", got, want) - } - }) - t.Run("named", func(t *testing.T) { - var m M - Set(&m, "foo", 1) - Set(&m, "bar", 1) - Set(&m, "bar", 2) - want := M{ - "foo": 1, - "bar": 2, - } - if got := m; !reflect.DeepEqual(got, want) { - t.Errorf("got %v; want %v", got, want) - } - }) -} - -func TestNonNil(t *testing.T) { - var s []string - NonNil(&s) - if len(s) != 0 { - t.Errorf("slice len = %d; want 0", len(s)) - } - if s == nil { - t.Error("slice still nil") - } - - s = append(s, "foo") - NonNil(&s) - if len(s) != 1 { - t.Errorf("len = %d; want 1", len(s)) - } - if s[0] != "foo" { - t.Errorf("value = %q; want foo", s) - } - - var m map[string]string - NonNil(&m) - if len(m) != 0 { - t.Errorf("map len = %d; want 0", len(s)) - } - if m == nil { - t.Error("map still nil") - } -} - -func TestNonNilMapForJSON(t *testing.T) { - type M map[string]int - var m M - NonNilMapForJSON(&m) - if m == nil { - t.Fatal("still nil") - } -} - -func TestNonNilSliceForJSON(t *testing.T) { - type S []int - var s S - NonNilSliceForJSON(&s) - if s == nil { - t.Fatal("still nil") - } -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package mak contains code to help make things.
+package mak
+
+import (
+ "reflect"
+ "testing"
+)
+
+type M map[string]int
+
+func TestSet(t *testing.T) {
+ t.Run("unnamed", func(t *testing.T) {
+ var m map[string]int
+ Set(&m, "foo", 42)
+ Set(&m, "bar", 1)
+ Set(&m, "bar", 2)
+ want := map[string]int{
+ "foo": 42,
+ "bar": 2,
+ }
+ if got := m; !reflect.DeepEqual(got, want) {
+ t.Errorf("got %v; want %v", got, want)
+ }
+ })
+ t.Run("named", func(t *testing.T) {
+ var m M
+ Set(&m, "foo", 1)
+ Set(&m, "bar", 1)
+ Set(&m, "bar", 2)
+ want := M{
+ "foo": 1,
+ "bar": 2,
+ }
+ if got := m; !reflect.DeepEqual(got, want) {
+ t.Errorf("got %v; want %v", got, want)
+ }
+ })
+}
+
+func TestNonNil(t *testing.T) {
+ var s []string
+ NonNil(&s)
+ if len(s) != 0 {
+ t.Errorf("slice len = %d; want 0", len(s))
+ }
+ if s == nil {
+ t.Error("slice still nil")
+ }
+
+ s = append(s, "foo")
+ NonNil(&s)
+ if len(s) != 1 {
+ t.Errorf("len = %d; want 1", len(s))
+ }
+ if s[0] != "foo" {
+ t.Errorf("value = %q; want foo", s)
+ }
+
+ var m map[string]string
+ NonNil(&m)
+ if len(m) != 0 {
+ t.Errorf("map len = %d; want 0", len(s))
+ }
+ if m == nil {
+ t.Error("map still nil")
+ }
+}
+
+func TestNonNilMapForJSON(t *testing.T) {
+ type M map[string]int
+ var m M
+ NonNilMapForJSON(&m)
+ if m == nil {
+ t.Fatal("still nil")
+ }
+}
+
+func TestNonNilSliceForJSON(t *testing.T) {
+ type S []int
+ var s S
+ NonNilSliceForJSON(&s)
+ if s == nil {
+ t.Fatal("still nil")
+ }
+}
diff --git a/util/multierr/multierr.go b/util/multierr/multierr.go index 93ca068f5..5ec36f644 100644 --- a/util/multierr/multierr.go +++ b/util/multierr/multierr.go @@ -1,136 +1,136 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -// Package multierr provides a simple multiple-error type. -// It was inspired by github.com/go-multierror/multierror. -package multierr - -import ( - "errors" - "slices" - "strings" -) - -// An Error represents multiple errors. -type Error struct { - errs []error -} - -// Error implements the error interface. -func (e Error) Error() string { - s := new(strings.Builder) - s.WriteString("multiple errors:") - for _, err := range e.errs { - s.WriteString("\n\t") - s.WriteString(err.Error()) - } - return s.String() -} - -// Errors returns a slice containing all errors in e. -func (e Error) Errors() []error { - return slices.Clone(e.errs) -} - -// Unwrap returns the underlying errors as-is. -func (e Error) Unwrap() []error { - // Do not clone since Unwrap requires callers to not mutate the slice. - // See the documentation in the Go "errors" package. - return e.errs -} - -// New returns an error composed from errs. -// Some errors in errs get special treatment: -// - nil errors are discarded -// - errors of type Error are expanded into the top level -// -// If the resulting slice has length 0, New returns nil. -// If the resulting slice has length 1, New returns that error. -// If the resulting slice has length > 1, New returns that slice as an Error. -func New(errs ...error) error { - // First count the number of errors to avoid allocating. - var n int - var errFirst error - for _, e := range errs { - switch e := e.(type) { - case nil: - continue - case Error: - n += len(e.errs) - if errFirst == nil && len(e.errs) > 0 { - errFirst = e.errs[0] - } - default: - n++ - if errFirst == nil { - errFirst = e - } - } - } - if n <= 1 { - return errFirst // nil if n == 0 - } - - // More than one error, allocate slice and construct the multi-error. - dst := make([]error, 0, n) - for _, e := range errs { - switch e := e.(type) { - case nil: - continue - case Error: - dst = append(dst, e.errs...) - default: - dst = append(dst, e) - } - } - return Error{errs: dst} -} - -// Is reports whether any error in e matches target. -func (e Error) Is(target error) bool { - for _, err := range e.errs { - if errors.Is(err, target) { - return true - } - } - return false -} - -// As finds the first error in e that matches target, and if any is found, -// sets target to that error value and returns true. Otherwise, it returns false. -func (e Error) As(target any) bool { - for _, err := range e.errs { - if ok := errors.As(err, target); ok { - return true - } - } - return false -} - -// Range performs a pre-order, depth-first iteration of the error tree -// by successively unwrapping all error values. -// For each iteration it calls fn with the current error value and -// stops iteration if it ever reports false. -func Range(err error, fn func(error) bool) bool { - if err == nil { - return true - } - if !fn(err) { - return false - } - switch err := err.(type) { - case interface{ Unwrap() error }: - if err := err.Unwrap(); err != nil { - if !Range(err, fn) { - return false - } - } - case interface{ Unwrap() []error }: - for _, err := range err.Unwrap() { - if !Range(err, fn) { - return false - } - } - } - return true -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package multierr provides a simple multiple-error type.
+// It was inspired by github.com/go-multierror/multierror.
+package multierr
+
+import (
+ "errors"
+ "slices"
+ "strings"
+)
+
+// An Error represents multiple errors.
+type Error struct {
+ errs []error
+}
+
+// Error implements the error interface.
+func (e Error) Error() string {
+ s := new(strings.Builder)
+ s.WriteString("multiple errors:")
+ for _, err := range e.errs {
+ s.WriteString("\n\t")
+ s.WriteString(err.Error())
+ }
+ return s.String()
+}
+
+// Errors returns a slice containing all errors in e.
+func (e Error) Errors() []error {
+ return slices.Clone(e.errs)
+}
+
+// Unwrap returns the underlying errors as-is.
+func (e Error) Unwrap() []error {
+ // Do not clone since Unwrap requires callers to not mutate the slice.
+ // See the documentation in the Go "errors" package.
+ return e.errs
+}
+
+// New returns an error composed from errs.
+// Some errors in errs get special treatment:
+// - nil errors are discarded
+// - errors of type Error are expanded into the top level
+//
+// If the resulting slice has length 0, New returns nil.
+// If the resulting slice has length 1, New returns that error.
+// If the resulting slice has length > 1, New returns that slice as an Error.
+func New(errs ...error) error {
+ // First count the number of errors to avoid allocating.
+ var n int
+ var errFirst error
+ for _, e := range errs {
+ switch e := e.(type) {
+ case nil:
+ continue
+ case Error:
+ n += len(e.errs)
+ if errFirst == nil && len(e.errs) > 0 {
+ errFirst = e.errs[0]
+ }
+ default:
+ n++
+ if errFirst == nil {
+ errFirst = e
+ }
+ }
+ }
+ if n <= 1 {
+ return errFirst // nil if n == 0
+ }
+
+ // More than one error, allocate slice and construct the multi-error.
+ dst := make([]error, 0, n)
+ for _, e := range errs {
+ switch e := e.(type) {
+ case nil:
+ continue
+ case Error:
+ dst = append(dst, e.errs...)
+ default:
+ dst = append(dst, e)
+ }
+ }
+ return Error{errs: dst}
+}
+
+// Is reports whether any error in e matches target.
+func (e Error) Is(target error) bool {
+ for _, err := range e.errs {
+ if errors.Is(err, target) {
+ return true
+ }
+ }
+ return false
+}
+
+// As finds the first error in e that matches target, and if any is found,
+// sets target to that error value and returns true. Otherwise, it returns false.
+func (e Error) As(target any) bool {
+ for _, err := range e.errs {
+ if ok := errors.As(err, target); ok {
+ return true
+ }
+ }
+ return false
+}
+
+// Range performs a pre-order, depth-first iteration of the error tree
+// by successively unwrapping all error values.
+// For each iteration it calls fn with the current error value and
+// stops iteration if it ever reports false.
+func Range(err error, fn func(error) bool) bool {
+ if err == nil {
+ return true
+ }
+ if !fn(err) {
+ return false
+ }
+ switch err := err.(type) {
+ case interface{ Unwrap() error }:
+ if err := err.Unwrap(); err != nil {
+ if !Range(err, fn) {
+ return false
+ }
+ }
+ case interface{ Unwrap() []error }:
+ for _, err := range err.Unwrap() {
+ if !Range(err, fn) {
+ return false
+ }
+ }
+ }
+ return true
+}
diff --git a/util/must/must.go b/util/must/must.go index 21965daa9..056986fca 100644 --- a/util/must/must.go +++ b/util/must/must.go @@ -1,25 +1,25 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -// Package must assists in calling functions that must succeed. -// -// Example usage: -// -// var target = must.Get(url.Parse(...)) -// must.Do(close()) -package must - -// Do panics if err is non-nil. -func Do(err error) { - if err != nil { - panic(err) - } -} - -// Get returns v as is. It panics if err is non-nil. -func Get[T any](v T, err error) T { - if err != nil { - panic(err) - } - return v -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package must assists in calling functions that must succeed.
+//
+// Example usage:
+//
+// var target = must.Get(url.Parse(...))
+// must.Do(close())
+package must
+
+// Do panics if err is non-nil.
+func Do(err error) {
+ if err != nil {
+ panic(err)
+ }
+}
+
+// Get returns v as is. It panics if err is non-nil.
+func Get[T any](v T, err error) T {
+ if err != nil {
+ panic(err)
+ }
+ return v
+}
diff --git a/util/osdiag/mksyscall.go b/util/osdiag/mksyscall.go index bcbe113b0..f20be7f92 100644 --- a/util/osdiag/mksyscall.go +++ b/util/osdiag/mksyscall.go @@ -1,13 +1,13 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package osdiag - -//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go mksyscall.go -//go:generate go run golang.org/x/tools/cmd/goimports -w zsyscall_windows.go - -//sys globalMemoryStatusEx(memStatus *_MEMORYSTATUSEX) (err error) [int32(failretval)==0] = kernel32.GlobalMemoryStatusEx -//sys regEnumValue(key registry.Key, index uint32, valueName *uint16, valueNameLen *uint32, reserved *uint32, valueType *uint32, pData *byte, cbData *uint32) (ret error) [failretval!=0] = advapi32.RegEnumValueW -//sys wscEnumProtocols(iProtocols *int32, protocolBuffer *wsaProtocolInfo, bufLen *uint32, errno *int32) (ret int32) = ws2_32.WSCEnumProtocols -//sys wscGetProviderInfo(providerId *windows.GUID, infoType _WSC_PROVIDER_INFO_TYPE, info unsafe.Pointer, infoSize *uintptr, flags uint32, errno *int32) (ret int32) = ws2_32.WSCGetProviderInfo -//sys wscGetProviderPath(providerId *windows.GUID, providerDllPath *uint16, providerDllPathLen *int32, errno *int32) (ret int32) = ws2_32.WSCGetProviderPath +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package osdiag
+
+//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go mksyscall.go
+//go:generate go run golang.org/x/tools/cmd/goimports -w zsyscall_windows.go
+
+//sys globalMemoryStatusEx(memStatus *_MEMORYSTATUSEX) (err error) [int32(failretval)==0] = kernel32.GlobalMemoryStatusEx
+//sys regEnumValue(key registry.Key, index uint32, valueName *uint16, valueNameLen *uint32, reserved *uint32, valueType *uint32, pData *byte, cbData *uint32) (ret error) [failretval!=0] = advapi32.RegEnumValueW
+//sys wscEnumProtocols(iProtocols *int32, protocolBuffer *wsaProtocolInfo, bufLen *uint32, errno *int32) (ret int32) = ws2_32.WSCEnumProtocols
+//sys wscGetProviderInfo(providerId *windows.GUID, infoType _WSC_PROVIDER_INFO_TYPE, info unsafe.Pointer, infoSize *uintptr, flags uint32, errno *int32) (ret int32) = ws2_32.WSCGetProviderInfo
+//sys wscGetProviderPath(providerId *windows.GUID, providerDllPath *uint16, providerDllPathLen *int32, errno *int32) (ret int32) = ws2_32.WSCGetProviderPath
diff --git a/util/osdiag/osdiag_windows_test.go b/util/osdiag/osdiag_windows_test.go index b29b602cc..776852a34 100644 --- a/util/osdiag/osdiag_windows_test.go +++ b/util/osdiag/osdiag_windows_test.go @@ -1,128 +1,128 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package osdiag - -import ( - "errors" - "fmt" - "maps" - "strings" - "testing" - - "golang.org/x/sys/windows/registry" -) - -func makeLongBinaryValue() []byte { - buf := make([]byte, maxBinaryValueLen*2) - for i, _ := range buf { - buf[i] = byte(i % 0xFF) - } - return buf -} - -var testData = map[string]any{ - "": "I am the default", - "StringEmpty": "", - "StringShort": "Hello", - "StringLong": strings.Repeat("7", initialValueBufLen+1), - "MultiStringEmpty": []string{}, - "MultiStringSingle": []string{"Foo"}, - "MultiStringSingleEmpty": []string{""}, - "MultiString": []string{"Foo", "Bar", "Baz"}, - "MultiStringWithEmptyBeginning": []string{"", "Foo", "Bar"}, - "MultiStringWithEmptyMiddle": []string{"Foo", "", "Bar"}, - "MultiStringWithEmptyEnd": []string{"Foo", "Bar", ""}, - "DWord": uint32(0x12345678), - "QWord": uint64(0x123456789abcdef0), - "BinaryEmpty": []byte{}, - "BinaryShort": []byte{0x01, 0x02, 0x03, 0x04}, - "BinaryLong": makeLongBinaryValue(), -} - -const ( - keyNameTest = `SOFTWARE\Tailscale Test` - subKeyNameTest = "SubKey" -) - -func setValues(t *testing.T, k registry.Key) { - for vk, v := range testData { - var err error - switch tv := v.(type) { - case string: - err = k.SetStringValue(vk, tv) - case []string: - err = k.SetStringsValue(vk, tv) - case uint32: - err = k.SetDWordValue(vk, tv) - case uint64: - err = k.SetQWordValue(vk, tv) - case []byte: - err = k.SetBinaryValue(vk, tv) - default: - t.Fatalf("Unknown type") - } - - if err != nil { - t.Fatalf("Error setting %q: %v", vk, err) - } - } -} - -func TestRegistrySupportInfo(t *testing.T) { - // Make sure the key doesn't exist yet - k, err := registry.OpenKey(registry.CURRENT_USER, keyNameTest, registry.READ) - switch { - case err == nil: - k.Close() - t.Fatalf("Test key already exists") - case !errors.Is(err, registry.ErrNotExist): - t.Fatal(err) - } - - func() { - k, _, err := registry.CreateKey(registry.CURRENT_USER, keyNameTest, registry.WRITE) - if err != nil { - t.Fatalf("Error creating test key: %v", err) - } - defer k.Close() - - setValues(t, k) - - sk, _, err := registry.CreateKey(k, subKeyNameTest, registry.WRITE) - if err != nil { - t.Fatalf("Error creating test subkey: %v", err) - } - defer sk.Close() - - setValues(t, sk) - }() - - t.Cleanup(func() { - registry.DeleteKey(registry.CURRENT_USER, keyNameTest+"\\"+subKeyNameTest) - registry.DeleteKey(registry.CURRENT_USER, keyNameTest) - }) - - wantValuesData := maps.Clone(testData) - wantValuesData["BinaryLong"] = (wantValuesData["BinaryLong"].([]byte))[:maxBinaryValueLen] - - wantKeyData := make(map[string]any) - maps.Copy(wantKeyData, wantValuesData) - wantSubKeyData := make(map[string]any) - maps.Copy(wantSubKeyData, wantValuesData) - wantKeyData[subKeyNameTest] = wantSubKeyData - - wantData := map[string]any{ - "HKCU\\" + keyNameTest: wantKeyData, - } - - gotData, err := getRegistrySupportInfo(registry.CURRENT_USER, []string{keyNameTest}) - if err != nil { - t.Errorf("getRegistrySupportInfo error: %v", err) - } - - want, got := fmt.Sprintf("%#v", wantData), fmt.Sprintf("%#v", gotData) - if want != got { - t.Errorf("Compare error: want\n%s,\ngot %s", want, got) - } -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package osdiag
+
+import (
+ "errors"
+ "fmt"
+ "maps"
+ "strings"
+ "testing"
+
+ "golang.org/x/sys/windows/registry"
+)
+
+func makeLongBinaryValue() []byte {
+ buf := make([]byte, maxBinaryValueLen*2)
+ for i, _ := range buf {
+ buf[i] = byte(i % 0xFF)
+ }
+ return buf
+}
+
+var testData = map[string]any{
+ "": "I am the default",
+ "StringEmpty": "",
+ "StringShort": "Hello",
+ "StringLong": strings.Repeat("7", initialValueBufLen+1),
+ "MultiStringEmpty": []string{},
+ "MultiStringSingle": []string{"Foo"},
+ "MultiStringSingleEmpty": []string{""},
+ "MultiString": []string{"Foo", "Bar", "Baz"},
+ "MultiStringWithEmptyBeginning": []string{"", "Foo", "Bar"},
+ "MultiStringWithEmptyMiddle": []string{"Foo", "", "Bar"},
+ "MultiStringWithEmptyEnd": []string{"Foo", "Bar", ""},
+ "DWord": uint32(0x12345678),
+ "QWord": uint64(0x123456789abcdef0),
+ "BinaryEmpty": []byte{},
+ "BinaryShort": []byte{0x01, 0x02, 0x03, 0x04},
+ "BinaryLong": makeLongBinaryValue(),
+}
+
+const (
+ keyNameTest = `SOFTWARE\Tailscale Test`
+ subKeyNameTest = "SubKey"
+)
+
+func setValues(t *testing.T, k registry.Key) {
+ for vk, v := range testData {
+ var err error
+ switch tv := v.(type) {
+ case string:
+ err = k.SetStringValue(vk, tv)
+ case []string:
+ err = k.SetStringsValue(vk, tv)
+ case uint32:
+ err = k.SetDWordValue(vk, tv)
+ case uint64:
+ err = k.SetQWordValue(vk, tv)
+ case []byte:
+ err = k.SetBinaryValue(vk, tv)
+ default:
+ t.Fatalf("Unknown type")
+ }
+
+ if err != nil {
+ t.Fatalf("Error setting %q: %v", vk, err)
+ }
+ }
+}
+
+func TestRegistrySupportInfo(t *testing.T) {
+ // Make sure the key doesn't exist yet
+ k, err := registry.OpenKey(registry.CURRENT_USER, keyNameTest, registry.READ)
+ switch {
+ case err == nil:
+ k.Close()
+ t.Fatalf("Test key already exists")
+ case !errors.Is(err, registry.ErrNotExist):
+ t.Fatal(err)
+ }
+
+ func() {
+ k, _, err := registry.CreateKey(registry.CURRENT_USER, keyNameTest, registry.WRITE)
+ if err != nil {
+ t.Fatalf("Error creating test key: %v", err)
+ }
+ defer k.Close()
+
+ setValues(t, k)
+
+ sk, _, err := registry.CreateKey(k, subKeyNameTest, registry.WRITE)
+ if err != nil {
+ t.Fatalf("Error creating test subkey: %v", err)
+ }
+ defer sk.Close()
+
+ setValues(t, sk)
+ }()
+
+ t.Cleanup(func() {
+ registry.DeleteKey(registry.CURRENT_USER, keyNameTest+"\\"+subKeyNameTest)
+ registry.DeleteKey(registry.CURRENT_USER, keyNameTest)
+ })
+
+ wantValuesData := maps.Clone(testData)
+ wantValuesData["BinaryLong"] = (wantValuesData["BinaryLong"].([]byte))[:maxBinaryValueLen]
+
+ wantKeyData := make(map[string]any)
+ maps.Copy(wantKeyData, wantValuesData)
+ wantSubKeyData := make(map[string]any)
+ maps.Copy(wantSubKeyData, wantValuesData)
+ wantKeyData[subKeyNameTest] = wantSubKeyData
+
+ wantData := map[string]any{
+ "HKCU\\" + keyNameTest: wantKeyData,
+ }
+
+ gotData, err := getRegistrySupportInfo(registry.CURRENT_USER, []string{keyNameTest})
+ if err != nil {
+ t.Errorf("getRegistrySupportInfo error: %v", err)
+ }
+
+ want, got := fmt.Sprintf("%#v", wantData), fmt.Sprintf("%#v", gotData)
+ if want != got {
+ t.Errorf("Compare error: want\n%s,\ngot %s", want, got)
+ }
+}
diff --git a/util/osshare/filesharingstatus_noop.go b/util/osshare/filesharingstatus_noop.go index 7f2b13190..6be4131a9 100644 --- a/util/osshare/filesharingstatus_noop.go +++ b/util/osshare/filesharingstatus_noop.go @@ -1,12 +1,12 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -//go:build !windows - -package osshare - -import ( - "tailscale.com/types/logger" -) - -func SetFileSharingEnabled(enabled bool, logf logger.Logf) {} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build !windows
+
+package osshare
+
+import (
+ "tailscale.com/types/logger"
+)
+
+func SetFileSharingEnabled(enabled bool, logf logger.Logf) {}
diff --git a/util/pidowner/pidowner.go b/util/pidowner/pidowner.go index 56bb640b7..62ea85d78 100644 --- a/util/pidowner/pidowner.go +++ b/util/pidowner/pidowner.go @@ -1,24 +1,24 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -// Package pidowner handles lookups from process ID to its owning user. -package pidowner - -import ( - "errors" - "runtime" -) - -var ErrNotImplemented = errors.New("not implemented for GOOS=" + runtime.GOOS) - -var ErrProcessNotFound = errors.New("process not found") - -// OwnerOfPID returns the user ID that owns the given process ID. -// -// The returned user ID is suitable to passing to os/user.LookupId. -// -// The returned error will be ErrNotImplemented for operating systems where -// this isn't supported. -func OwnerOfPID(pid int) (userID string, err error) { - return ownerOfPID(pid) -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package pidowner handles lookups from process ID to its owning user.
+package pidowner
+
+import (
+ "errors"
+ "runtime"
+)
+
+var ErrNotImplemented = errors.New("not implemented for GOOS=" + runtime.GOOS)
+
+var ErrProcessNotFound = errors.New("process not found")
+
+// OwnerOfPID returns the user ID that owns the given process ID.
+//
+// The returned user ID is suitable to passing to os/user.LookupId.
+//
+// The returned error will be ErrNotImplemented for operating systems where
+// this isn't supported.
+func OwnerOfPID(pid int) (userID string, err error) {
+ return ownerOfPID(pid)
+}
diff --git a/util/pidowner/pidowner_noimpl.go b/util/pidowner/pidowner_noimpl.go index 50add492f..a631e3f24 100644 --- a/util/pidowner/pidowner_noimpl.go +++ b/util/pidowner/pidowner_noimpl.go @@ -1,8 +1,8 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -//go:build !windows && !linux - -package pidowner - -func ownerOfPID(pid int) (userID string, err error) { return "", ErrNotImplemented } +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build !windows && !linux
+
+package pidowner
+
+func ownerOfPID(pid int) (userID string, err error) { return "", ErrNotImplemented }
diff --git a/util/pidowner/pidowner_windows.go b/util/pidowner/pidowner_windows.go index dbf13ac81..c7b2512a4 100644 --- a/util/pidowner/pidowner_windows.go +++ b/util/pidowner/pidowner_windows.go @@ -1,35 +1,35 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package pidowner - -import ( - "fmt" - "syscall" - - "golang.org/x/sys/windows" -) - -func ownerOfPID(pid int) (userID string, err error) { - procHnd, err := windows.OpenProcess(windows.PROCESS_QUERY_INFORMATION, false, uint32(pid)) - if err == syscall.Errno(0x57) { // invalid parameter, for PIDs that don't exist - return "", ErrProcessNotFound - } - if err != nil { - return "", fmt.Errorf("OpenProcess: %T %#v", err, err) - } - defer windows.CloseHandle(procHnd) - - var tok windows.Token - if err := windows.OpenProcessToken(procHnd, windows.TOKEN_QUERY, &tok); err != nil { - return "", fmt.Errorf("OpenProcessToken: %w", err) - } - - tokUser, err := tok.GetTokenUser() - if err != nil { - return "", fmt.Errorf("GetTokenUser: %w", err) - } - - sid := tokUser.User.Sid - return sid.String(), nil -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package pidowner
+
+import (
+ "fmt"
+ "syscall"
+
+ "golang.org/x/sys/windows"
+)
+
+func ownerOfPID(pid int) (userID string, err error) {
+ procHnd, err := windows.OpenProcess(windows.PROCESS_QUERY_INFORMATION, false, uint32(pid))
+ if err == syscall.Errno(0x57) { // invalid parameter, for PIDs that don't exist
+ return "", ErrProcessNotFound
+ }
+ if err != nil {
+ return "", fmt.Errorf("OpenProcess: %T %#v", err, err)
+ }
+ defer windows.CloseHandle(procHnd)
+
+ var tok windows.Token
+ if err := windows.OpenProcessToken(procHnd, windows.TOKEN_QUERY, &tok); err != nil {
+ return "", fmt.Errorf("OpenProcessToken: %w", err)
+ }
+
+ tokUser, err := tok.GetTokenUser()
+ if err != nil {
+ return "", fmt.Errorf("GetTokenUser: %w", err)
+ }
+
+ sid := tokUser.User.Sid
+ return sid.String(), nil
+}
diff --git a/util/precompress/precompress.go b/util/precompress/precompress.go index 6d1a26efd..e9bebb333 100644 --- a/util/precompress/precompress.go +++ b/util/precompress/precompress.go @@ -1,129 +1,129 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -// Package precompress provides build- and serving-time support for -// precompressed static resources, to avoid the cost of repeatedly compressing -// unchanging resources. -package precompress - -import ( - "bytes" - "compress/gzip" - "io" - "io/fs" - "net/http" - "os" - "path" - "path/filepath" - - "github.com/andybalholm/brotli" - "golang.org/x/sync/errgroup" - "tailscale.com/tsweb" -) - -// PrecompressDir compresses static assets in dirPath using Gzip and Brotli, so -// that they can be later served with OpenPrecompressedFile. -func PrecompressDir(dirPath string, options Options) error { - var eg errgroup.Group - err := fs.WalkDir(os.DirFS(dirPath), ".", func(p string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - if d.IsDir() { - return nil - } - if !compressibleExtensions[filepath.Ext(p)] { - return nil - } - p = path.Join(dirPath, p) - if options.ProgressFn != nil { - options.ProgressFn(p) - } - - eg.Go(func() error { - return Precompress(p, options) - }) - return nil - }) - if err != nil { - return err - } - return eg.Wait() -} - -type Options struct { - // FastCompression controls whether compression should be optimized for - // speed rather than size. - FastCompression bool - // ProgressFn, if non-nil, is invoked when a file in the directory is about - // to be compressed. - ProgressFn func(path string) -} - -// OpenPrecompressedFile opens a file from fs, preferring compressed versions -// generated by PrecompressDir if possible. -func OpenPrecompressedFile(w http.ResponseWriter, r *http.Request, path string, fs fs.FS) (fs.File, error) { - if tsweb.AcceptsEncoding(r, "br") { - if f, err := fs.Open(path + ".br"); err == nil { - w.Header().Set("Content-Encoding", "br") - return f, nil - } - } - if tsweb.AcceptsEncoding(r, "gzip") { - if f, err := fs.Open(path + ".gz"); err == nil { - w.Header().Set("Content-Encoding", "gzip") - return f, nil - } - } - - return fs.Open(path) -} - -var compressibleExtensions = map[string]bool{ - ".js": true, - ".css": true, -} - -func Precompress(path string, options Options) error { - contents, err := os.ReadFile(path) - if err != nil { - return err - } - fi, err := os.Lstat(path) - if err != nil { - return err - } - - gzipLevel := gzip.BestCompression - if options.FastCompression { - gzipLevel = gzip.BestSpeed - } - err = writeCompressed(contents, func(w io.Writer) (io.WriteCloser, error) { - return gzip.NewWriterLevel(w, gzipLevel) - }, path+".gz", fi.Mode()) - if err != nil { - return err - } - brotliLevel := brotli.BestCompression - if options.FastCompression { - brotliLevel = brotli.BestSpeed - } - return writeCompressed(contents, func(w io.Writer) (io.WriteCloser, error) { - return brotli.NewWriterLevel(w, brotliLevel), nil - }, path+".br", fi.Mode()) -} - -func writeCompressed(contents []byte, compressedWriterCreator func(io.Writer) (io.WriteCloser, error), outputPath string, outputMode fs.FileMode) error { - var buf bytes.Buffer - compressedWriter, err := compressedWriterCreator(&buf) - if err != nil { - return err - } - if _, err := compressedWriter.Write(contents); err != nil { - return err - } - if err := compressedWriter.Close(); err != nil { - return err - } - return os.WriteFile(outputPath, buf.Bytes(), outputMode) -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package precompress provides build- and serving-time support for
+// precompressed static resources, to avoid the cost of repeatedly compressing
+// unchanging resources.
+package precompress
+
+import (
+ "bytes"
+ "compress/gzip"
+ "io"
+ "io/fs"
+ "net/http"
+ "os"
+ "path"
+ "path/filepath"
+
+ "github.com/andybalholm/brotli"
+ "golang.org/x/sync/errgroup"
+ "tailscale.com/tsweb"
+)
+
+// PrecompressDir compresses static assets in dirPath using Gzip and Brotli, so
+// that they can be later served with OpenPrecompressedFile.
+func PrecompressDir(dirPath string, options Options) error {
+ var eg errgroup.Group
+ err := fs.WalkDir(os.DirFS(dirPath), ".", func(p string, d fs.DirEntry, err error) error {
+ if err != nil {
+ return err
+ }
+ if d.IsDir() {
+ return nil
+ }
+ if !compressibleExtensions[filepath.Ext(p)] {
+ return nil
+ }
+ p = path.Join(dirPath, p)
+ if options.ProgressFn != nil {
+ options.ProgressFn(p)
+ }
+
+ eg.Go(func() error {
+ return Precompress(p, options)
+ })
+ return nil
+ })
+ if err != nil {
+ return err
+ }
+ return eg.Wait()
+}
+
+type Options struct {
+ // FastCompression controls whether compression should be optimized for
+ // speed rather than size.
+ FastCompression bool
+ // ProgressFn, if non-nil, is invoked when a file in the directory is about
+ // to be compressed.
+ ProgressFn func(path string)
+}
+
+// OpenPrecompressedFile opens a file from fs, preferring compressed versions
+// generated by PrecompressDir if possible.
+func OpenPrecompressedFile(w http.ResponseWriter, r *http.Request, path string, fs fs.FS) (fs.File, error) {
+ if tsweb.AcceptsEncoding(r, "br") {
+ if f, err := fs.Open(path + ".br"); err == nil {
+ w.Header().Set("Content-Encoding", "br")
+ return f, nil
+ }
+ }
+ if tsweb.AcceptsEncoding(r, "gzip") {
+ if f, err := fs.Open(path + ".gz"); err == nil {
+ w.Header().Set("Content-Encoding", "gzip")
+ return f, nil
+ }
+ }
+
+ return fs.Open(path)
+}
+
+var compressibleExtensions = map[string]bool{
+ ".js": true,
+ ".css": true,
+}
+
+func Precompress(path string, options Options) error {
+ contents, err := os.ReadFile(path)
+ if err != nil {
+ return err
+ }
+ fi, err := os.Lstat(path)
+ if err != nil {
+ return err
+ }
+
+ gzipLevel := gzip.BestCompression
+ if options.FastCompression {
+ gzipLevel = gzip.BestSpeed
+ }
+ err = writeCompressed(contents, func(w io.Writer) (io.WriteCloser, error) {
+ return gzip.NewWriterLevel(w, gzipLevel)
+ }, path+".gz", fi.Mode())
+ if err != nil {
+ return err
+ }
+ brotliLevel := brotli.BestCompression
+ if options.FastCompression {
+ brotliLevel = brotli.BestSpeed
+ }
+ return writeCompressed(contents, func(w io.Writer) (io.WriteCloser, error) {
+ return brotli.NewWriterLevel(w, brotliLevel), nil
+ }, path+".br", fi.Mode())
+}
+
+func writeCompressed(contents []byte, compressedWriterCreator func(io.Writer) (io.WriteCloser, error), outputPath string, outputMode fs.FileMode) error {
+ var buf bytes.Buffer
+ compressedWriter, err := compressedWriterCreator(&buf)
+ if err != nil {
+ return err
+ }
+ if _, err := compressedWriter.Write(contents); err != nil {
+ return err
+ }
+ if err := compressedWriter.Close(); err != nil {
+ return err
+ }
+ return os.WriteFile(outputPath, buf.Bytes(), outputMode)
+}
diff --git a/util/quarantine/quarantine.go b/util/quarantine/quarantine.go index 7ad65a81d..488465ba0 100644 --- a/util/quarantine/quarantine.go +++ b/util/quarantine/quarantine.go @@ -1,14 +1,14 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -// Package quarantine sets platform specific "quarantine" attributes on files -// that are received from other hosts. -package quarantine - -import "os" - -// SetOnFile sets the platform-specific quarantine attribute (if any) on the -// provided file. -func SetOnFile(f *os.File) error { - return setQuarantineAttr(f) -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package quarantine sets platform specific "quarantine" attributes on files
+// that are received from other hosts.
+package quarantine
+
+import "os"
+
+// SetOnFile sets the platform-specific quarantine attribute (if any) on the
+// provided file.
+func SetOnFile(f *os.File) error {
+ return setQuarantineAttr(f)
+}
diff --git a/util/quarantine/quarantine_darwin.go b/util/quarantine/quarantine_darwin.go index 35405d9cc..b7757f334 100644 --- a/util/quarantine/quarantine_darwin.go +++ b/util/quarantine/quarantine_darwin.go @@ -1,56 +1,56 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package quarantine - -import ( - "fmt" - "os" - "strings" - "time" - - "github.com/google/uuid" - "golang.org/x/sys/unix" -) - -func setQuarantineAttr(f *os.File) error { - sc, err := f.SyscallConn() - if err != nil { - return err - } - - now := time.Now() - - // We uppercase the UUID to match what other applications on macOS do - id := strings.ToUpper(uuid.New().String()) - - // kLSQuarantineTypeOtherDownload; this matches what AirDrop sets when - // receiving a file. - quarantineType := "0001" - - // This format is under-documented, but the following links contain a - // reasonably comprehensive overview: - // https://eclecticlight.co/2020/10/29/quarantine-and-the-quarantine-flag/ - // https://nixhacker.com/security-protection-in-macos-1/ - // https://ilostmynotes.blogspot.com/2012/06/gatekeeper-xprotect-and-quarantine.html - attrData := fmt.Sprintf("%s;%x;%s;%s", - quarantineType, // quarantine value - now.Unix(), // time in hex - "Tailscale", // application - id, // UUID - ) - - var innerErr error - err = sc.Control(func(fd uintptr) { - innerErr = unix.Fsetxattr( - int(fd), - "com.apple.quarantine", // attr - []byte(attrData), - 0, - ) - }) - if err != nil { - return err - } - return innerErr -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package quarantine
+
+import (
+ "fmt"
+ "os"
+ "strings"
+ "time"
+
+ "github.com/google/uuid"
+ "golang.org/x/sys/unix"
+)
+
+func setQuarantineAttr(f *os.File) error {
+ sc, err := f.SyscallConn()
+ if err != nil {
+ return err
+ }
+
+ now := time.Now()
+
+ // We uppercase the UUID to match what other applications on macOS do
+ id := strings.ToUpper(uuid.New().String())
+
+ // kLSQuarantineTypeOtherDownload; this matches what AirDrop sets when
+ // receiving a file.
+ quarantineType := "0001"
+
+ // This format is under-documented, but the following links contain a
+ // reasonably comprehensive overview:
+ // https://eclecticlight.co/2020/10/29/quarantine-and-the-quarantine-flag/
+ // https://nixhacker.com/security-protection-in-macos-1/
+ // https://ilostmynotes.blogspot.com/2012/06/gatekeeper-xprotect-and-quarantine.html
+ attrData := fmt.Sprintf("%s;%x;%s;%s",
+ quarantineType, // quarantine value
+ now.Unix(), // time in hex
+ "Tailscale", // application
+ id, // UUID
+ )
+
+ var innerErr error
+ err = sc.Control(func(fd uintptr) {
+ innerErr = unix.Fsetxattr(
+ int(fd),
+ "com.apple.quarantine", // attr
+ []byte(attrData),
+ 0,
+ )
+ })
+ if err != nil {
+ return err
+ }
+ return innerErr
+}
diff --git a/util/quarantine/quarantine_default.go b/util/quarantine/quarantine_default.go index 65954a4d2..65a14ed26 100644 --- a/util/quarantine/quarantine_default.go +++ b/util/quarantine/quarantine_default.go @@ -1,14 +1,14 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -//go:build !darwin && !windows - -package quarantine - -import ( - "os" -) - -func setQuarantineAttr(f *os.File) error { - return nil -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build !darwin && !windows
+
+package quarantine
+
+import (
+ "os"
+)
+
+func setQuarantineAttr(f *os.File) error {
+ return nil
+}
diff --git a/util/quarantine/quarantine_windows.go b/util/quarantine/quarantine_windows.go index 6fdf4e699..3052c2c6d 100644 --- a/util/quarantine/quarantine_windows.go +++ b/util/quarantine/quarantine_windows.go @@ -1,29 +1,29 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package quarantine - -import ( - "os" - "strings" -) - -func setQuarantineAttr(f *os.File) error { - // Documentation on this can be found here: - // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/6e3f7352-d11c-4d76-8c39-2516a9df36e8 - // - // Additional information can be found at: - // https://www.digital-detective.net/forensic-analysis-of-zone-identifier-stream/ - // https://bugzilla.mozilla.org/show_bug.cgi?id=1433179 - content := strings.Join([]string{ - "[ZoneTransfer]", - - // "URLZONE_INTERNET" - // https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/ms537175(v=vs.85) - "ZoneId=3", - - // TODO(andrew): should/could we add ReferrerUrl or HostUrl? - }, "\r\n") - - return os.WriteFile(f.Name()+":Zone.Identifier", []byte(content), 0) -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package quarantine
+
+import (
+ "os"
+ "strings"
+)
+
+func setQuarantineAttr(f *os.File) error {
+ // Documentation on this can be found here:
+ // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/6e3f7352-d11c-4d76-8c39-2516a9df36e8
+ //
+ // Additional information can be found at:
+ // https://www.digital-detective.net/forensic-analysis-of-zone-identifier-stream/
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1433179
+ content := strings.Join([]string{
+ "[ZoneTransfer]",
+
+ // "URLZONE_INTERNET"
+ // https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/ms537175(v=vs.85)
+ "ZoneId=3",
+
+ // TODO(andrew): should/could we add ReferrerUrl or HostUrl?
+ }, "\r\n")
+
+ return os.WriteFile(f.Name()+":Zone.Identifier", []byte(content), 0)
+}
diff --git a/util/race/race_test.go b/util/race/race_test.go index d38382712..17ea76459 100644 --- a/util/race/race_test.go +++ b/util/race/race_test.go @@ -1,99 +1,99 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package race - -import ( - "context" - "errors" - "testing" - "time" - - "tailscale.com/tstest" -) - -func TestRaceSuccess1(t *testing.T) { - tstest.ResourceCheck(t) - - const want = "success" - rh := New[string]( - 10*time.Second, - func(context.Context) (string, error) { - return want, nil - }, func(context.Context) (string, error) { - t.Fatal("should not be called") - return "", nil - }) - res, err := rh.Start(context.Background()) - if err != nil { - t.Fatal(err) - } - if res != want { - t.Errorf("got res=%q, want %q", res, want) - } -} - -func TestRaceRetry(t *testing.T) { - tstest.ResourceCheck(t) - - const want = "fallback" - rh := New[string]( - 10*time.Second, - func(context.Context) (string, error) { - return "", errors.New("some error") - }, func(context.Context) (string, error) { - return want, nil - }) - res, err := rh.Start(context.Background()) - if err != nil { - t.Fatal(err) - } - if res != want { - t.Errorf("got res=%q, want %q", res, want) - } -} - -func TestRaceTimeout(t *testing.T) { - tstest.ResourceCheck(t) - - const want = "fallback" - rh := New[string]( - 100*time.Millisecond, - func(ctx context.Context) (string, error) { - // Block forever - <-ctx.Done() - return "", ctx.Err() - }, func(context.Context) (string, error) { - return want, nil - }) - res, err := rh.Start(context.Background()) - if err != nil { - t.Fatal(err) - } - if res != want { - t.Errorf("got res=%q, want %q", res, want) - } -} - -func TestRaceError(t *testing.T) { - tstest.ResourceCheck(t) - - err1 := errors.New("error 1") - err2 := errors.New("error 2") - - rh := New[string]( - 100*time.Millisecond, - func(ctx context.Context) (string, error) { - return "", err1 - }, func(context.Context) (string, error) { - return "", err2 - }) - - _, err := rh.Start(context.Background()) - if !errors.Is(err, err1) { - t.Errorf("wanted err to contain err1; got %v", err) - } - if !errors.Is(err, err2) { - t.Errorf("wanted err to contain err2; got %v", err) - } -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package race
+
+import (
+ "context"
+ "errors"
+ "testing"
+ "time"
+
+ "tailscale.com/tstest"
+)
+
+func TestRaceSuccess1(t *testing.T) {
+ tstest.ResourceCheck(t)
+
+ const want = "success"
+ rh := New[string](
+ 10*time.Second,
+ func(context.Context) (string, error) {
+ return want, nil
+ }, func(context.Context) (string, error) {
+ t.Fatal("should not be called")
+ return "", nil
+ })
+ res, err := rh.Start(context.Background())
+ if err != nil {
+ t.Fatal(err)
+ }
+ if res != want {
+ t.Errorf("got res=%q, want %q", res, want)
+ }
+}
+
+func TestRaceRetry(t *testing.T) {
+ tstest.ResourceCheck(t)
+
+ const want = "fallback"
+ rh := New[string](
+ 10*time.Second,
+ func(context.Context) (string, error) {
+ return "", errors.New("some error")
+ }, func(context.Context) (string, error) {
+ return want, nil
+ })
+ res, err := rh.Start(context.Background())
+ if err != nil {
+ t.Fatal(err)
+ }
+ if res != want {
+ t.Errorf("got res=%q, want %q", res, want)
+ }
+}
+
+func TestRaceTimeout(t *testing.T) {
+ tstest.ResourceCheck(t)
+
+ const want = "fallback"
+ rh := New[string](
+ 100*time.Millisecond,
+ func(ctx context.Context) (string, error) {
+ // Block forever
+ <-ctx.Done()
+ return "", ctx.Err()
+ }, func(context.Context) (string, error) {
+ return want, nil
+ })
+ res, err := rh.Start(context.Background())
+ if err != nil {
+ t.Fatal(err)
+ }
+ if res != want {
+ t.Errorf("got res=%q, want %q", res, want)
+ }
+}
+
+func TestRaceError(t *testing.T) {
+ tstest.ResourceCheck(t)
+
+ err1 := errors.New("error 1")
+ err2 := errors.New("error 2")
+
+ rh := New[string](
+ 100*time.Millisecond,
+ func(ctx context.Context) (string, error) {
+ return "", err1
+ }, func(context.Context) (string, error) {
+ return "", err2
+ })
+
+ _, err := rh.Start(context.Background())
+ if !errors.Is(err, err1) {
+ t.Errorf("wanted err to contain err1; got %v", err)
+ }
+ if !errors.Is(err, err2) {
+ t.Errorf("wanted err to contain err2; got %v", err)
+ }
+}
diff --git a/util/racebuild/off.go b/util/racebuild/off.go index 8f4fe998f..a0dba0f32 100644 --- a/util/racebuild/off.go +++ b/util/racebuild/off.go @@ -1,8 +1,8 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -//go:build !race - -package racebuild - -const On = false +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build !race
+
+package racebuild
+
+const On = false
diff --git a/util/racebuild/on.go b/util/racebuild/on.go index 69ae2bcae..c60bca2e6 100644 --- a/util/racebuild/on.go +++ b/util/racebuild/on.go @@ -1,8 +1,8 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -//go:build race - -package racebuild - -const On = true +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build race
+
+package racebuild
+
+const On = true
diff --git a/util/racebuild/racebuild.go b/util/racebuild/racebuild.go index d061276cb..c1a43eb96 100644 --- a/util/racebuild/racebuild.go +++ b/util/racebuild/racebuild.go @@ -1,6 +1,6 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -// Package racebuild exports a constant about whether the current binary -// was built with the race detector. -package racebuild +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package racebuild exports a constant about whether the current binary
+// was built with the race detector.
+package racebuild
diff --git a/util/rands/rands.go b/util/rands/rands.go index d83e1e558..dcd75c5f3 100644 --- a/util/rands/rands.go +++ b/util/rands/rands.go @@ -1,25 +1,25 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -// Package rands contains utility functions for randomness. -package rands - -import ( - crand "crypto/rand" - "encoding/hex" -) - -// HexString returns a string of n cryptographically random lowercase -// hex characters. -// -// That is, HexString(3) returns something like "0fc", containing 12 -// bits of randomness. -func HexString(n int) string { - nb := n / 2 - if n%2 == 1 { - nb++ - } - b := make([]byte, nb) - crand.Read(b) - return hex.EncodeToString(b)[:n] -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package rands contains utility functions for randomness.
+package rands
+
+import (
+ crand "crypto/rand"
+ "encoding/hex"
+)
+
+// HexString returns a string of n cryptographically random lowercase
+// hex characters.
+//
+// That is, HexString(3) returns something like "0fc", containing 12
+// bits of randomness.
+func HexString(n int) string {
+ nb := n / 2
+ if n%2 == 1 {
+ nb++
+ }
+ b := make([]byte, nb)
+ crand.Read(b)
+ return hex.EncodeToString(b)[:n]
+}
diff --git a/util/rands/rands_test.go b/util/rands/rands_test.go index 5813f2bb4..ec339f94b 100644 --- a/util/rands/rands_test.go +++ b/util/rands/rands_test.go @@ -1,15 +1,15 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package rands - -import "testing" - -func TestHexString(t *testing.T) { - for i := 0; i <= 8; i++ { - s := HexString(i) - if len(s) != i { - t.Errorf("HexString(%v) = %q; want len %v, not %v", i, s, i, len(s)) - } - } -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package rands
+
+import "testing"
+
+func TestHexString(t *testing.T) {
+ for i := 0; i <= 8; i++ {
+ s := HexString(i)
+ if len(s) != i {
+ t.Errorf("HexString(%v) = %q; want len %v, not %v", i, s, i, len(s))
+ }
+ }
+}
diff --git a/util/set/handle.go b/util/set/handle.go index 471ceeba2..61b4eb93d 100644 --- a/util/set/handle.go +++ b/util/set/handle.go @@ -1,28 +1,28 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package set - -// HandleSet is a set of T. -// -// It is not safe for concurrent use. -type HandleSet[T any] map[Handle]T - -// Handle is an opaque comparable value that's used as the map key in a -// HandleSet. The only way to get one is to call HandleSet.Add. -type Handle struct { - v *byte -} - -// Add adds the element (map value) e to the set. -// -// It returns the handle (map key) with which e can be removed, using a map -// delete. -func (s *HandleSet[T]) Add(e T) Handle { - h := Handle{new(byte)} - if *s == nil { - *s = make(HandleSet[T]) - } - (*s)[h] = e - return h -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package set
+
+// HandleSet is a set of T.
+//
+// It is not safe for concurrent use.
+type HandleSet[T any] map[Handle]T
+
+// Handle is an opaque comparable value that's used as the map key in a
+// HandleSet. The only way to get one is to call HandleSet.Add.
+type Handle struct {
+ v *byte
+}
+
+// Add adds the element (map value) e to the set.
+//
+// It returns the handle (map key) with which e can be removed, using a map
+// delete.
+func (s *HandleSet[T]) Add(e T) Handle {
+ h := Handle{new(byte)}
+ if *s == nil {
+ *s = make(HandleSet[T])
+ }
+ (*s)[h] = e
+ return h
+}
diff --git a/util/set/slice_test.go b/util/set/slice_test.go index 9134c2962..ca57e52e8 100644 --- a/util/set/slice_test.go +++ b/util/set/slice_test.go @@ -1,56 +1,56 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package set - -import ( - "testing" - - qt "github.com/frankban/quicktest" -) - -func TestSliceSet(t *testing.T) { - c := qt.New(t) - - var ss Slice[int] - c.Check(len(ss.slice), qt.Equals, 0) - ss.Add(1) - c.Check(len(ss.slice), qt.Equals, 1) - c.Check(len(ss.set), qt.Equals, 0) - c.Check(ss.Contains(1), qt.Equals, true) - c.Check(ss.Contains(2), qt.Equals, false) - - ss.Add(1) - c.Check(len(ss.slice), qt.Equals, 1) - c.Check(len(ss.set), qt.Equals, 0) - - ss.Add(2) - ss.Add(3) - ss.Add(4) - ss.Add(5) - ss.Add(6) - ss.Add(7) - ss.Add(8) - c.Check(len(ss.slice), qt.Equals, 8) - c.Check(len(ss.set), qt.Equals, 0) - - ss.Add(9) - c.Check(len(ss.slice), qt.Equals, 9) - c.Check(len(ss.set), qt.Equals, 9) - - ss.Remove(4) - c.Check(len(ss.slice), qt.Equals, 8) - c.Check(len(ss.set), qt.Equals, 8) - c.Assert(ss.Contains(4), qt.IsFalse) - - // Ensure that the order of insertion is maintained - c.Assert(ss.Slice().AsSlice(), qt.DeepEquals, []int{1, 2, 3, 5, 6, 7, 8, 9}) - ss.Add(4) - c.Check(len(ss.slice), qt.Equals, 9) - c.Check(len(ss.set), qt.Equals, 9) - c.Assert(ss.Contains(4), qt.IsTrue) - c.Assert(ss.Slice().AsSlice(), qt.DeepEquals, []int{1, 2, 3, 5, 6, 7, 8, 9, 4}) - - ss.Add(1, 234, 556) - c.Assert(ss.Slice().AsSlice(), qt.DeepEquals, []int{1, 2, 3, 5, 6, 7, 8, 9, 4, 234, 556}) -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package set
+
+import (
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+)
+
+func TestSliceSet(t *testing.T) {
+ c := qt.New(t)
+
+ var ss Slice[int]
+ c.Check(len(ss.slice), qt.Equals, 0)
+ ss.Add(1)
+ c.Check(len(ss.slice), qt.Equals, 1)
+ c.Check(len(ss.set), qt.Equals, 0)
+ c.Check(ss.Contains(1), qt.Equals, true)
+ c.Check(ss.Contains(2), qt.Equals, false)
+
+ ss.Add(1)
+ c.Check(len(ss.slice), qt.Equals, 1)
+ c.Check(len(ss.set), qt.Equals, 0)
+
+ ss.Add(2)
+ ss.Add(3)
+ ss.Add(4)
+ ss.Add(5)
+ ss.Add(6)
+ ss.Add(7)
+ ss.Add(8)
+ c.Check(len(ss.slice), qt.Equals, 8)
+ c.Check(len(ss.set), qt.Equals, 0)
+
+ ss.Add(9)
+ c.Check(len(ss.slice), qt.Equals, 9)
+ c.Check(len(ss.set), qt.Equals, 9)
+
+ ss.Remove(4)
+ c.Check(len(ss.slice), qt.Equals, 8)
+ c.Check(len(ss.set), qt.Equals, 8)
+ c.Assert(ss.Contains(4), qt.IsFalse)
+
+ // Ensure that the order of insertion is maintained
+ c.Assert(ss.Slice().AsSlice(), qt.DeepEquals, []int{1, 2, 3, 5, 6, 7, 8, 9})
+ ss.Add(4)
+ c.Check(len(ss.slice), qt.Equals, 9)
+ c.Check(len(ss.set), qt.Equals, 9)
+ c.Assert(ss.Contains(4), qt.IsTrue)
+ c.Assert(ss.Slice().AsSlice(), qt.DeepEquals, []int{1, 2, 3, 5, 6, 7, 8, 9, 4})
+
+ ss.Add(1, 234, 556)
+ c.Assert(ss.Slice().AsSlice(), qt.DeepEquals, []int{1, 2, 3, 5, 6, 7, 8, 9, 4, 234, 556})
+}
diff --git a/util/sysresources/memory.go b/util/sysresources/memory.go index 7363155cd..8bf784e13 100644 --- a/util/sysresources/memory.go +++ b/util/sysresources/memory.go @@ -1,10 +1,10 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package sysresources - -// TotalMemory returns the total accessible system memory, in bytes. If the -// value cannot be determined, then 0 will be returned. -func TotalMemory() uint64 { - return totalMemoryImpl() -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package sysresources
+
+// TotalMemory returns the total accessible system memory, in bytes. If the
+// value cannot be determined, then 0 will be returned.
+func TotalMemory() uint64 {
+ return totalMemoryImpl()
+}
diff --git a/util/sysresources/memory_bsd.go b/util/sysresources/memory_bsd.go index 26850dce6..39d3a18a9 100644 --- a/util/sysresources/memory_bsd.go +++ b/util/sysresources/memory_bsd.go @@ -1,16 +1,16 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -//go:build freebsd || openbsd || dragonfly || netbsd - -package sysresources - -import "golang.org/x/sys/unix" - -func totalMemoryImpl() uint64 { - val, err := unix.SysctlUint64("hw.physmem") - if err != nil { - return 0 - } - return val -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build freebsd || openbsd || dragonfly || netbsd
+
+package sysresources
+
+import "golang.org/x/sys/unix"
+
+func totalMemoryImpl() uint64 {
+ val, err := unix.SysctlUint64("hw.physmem")
+ if err != nil {
+ return 0
+ }
+ return val
+}
diff --git a/util/sysresources/memory_darwin.go b/util/sysresources/memory_darwin.go index e07bac0cd..2f74b6cec 100644 --- a/util/sysresources/memory_darwin.go +++ b/util/sysresources/memory_darwin.go @@ -1,16 +1,16 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -//go:build darwin - -package sysresources - -import "golang.org/x/sys/unix" - -func totalMemoryImpl() uint64 { - val, err := unix.SysctlUint64("hw.memsize") - if err != nil { - return 0 - } - return val -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build darwin
+
+package sysresources
+
+import "golang.org/x/sys/unix"
+
+func totalMemoryImpl() uint64 {
+ val, err := unix.SysctlUint64("hw.memsize")
+ if err != nil {
+ return 0
+ }
+ return val
+}
diff --git a/util/sysresources/memory_linux.go b/util/sysresources/memory_linux.go index 0239b0e80..f3c51469f 100644 --- a/util/sysresources/memory_linux.go +++ b/util/sysresources/memory_linux.go @@ -1,19 +1,19 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -//go:build linux - -package sysresources - -import "golang.org/x/sys/unix" - -func totalMemoryImpl() uint64 { - var info unix.Sysinfo_t - - if err := unix.Sysinfo(&info); err != nil { - return 0 - } - - // uint64 casts are required since these might be uint32s - return uint64(info.Totalram) * uint64(info.Unit) -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build linux
+
+package sysresources
+
+import "golang.org/x/sys/unix"
+
+func totalMemoryImpl() uint64 {
+ var info unix.Sysinfo_t
+
+ if err := unix.Sysinfo(&info); err != nil {
+ return 0
+ }
+
+ // uint64 casts are required since these might be uint32s
+ return uint64(info.Totalram) * uint64(info.Unit)
+}
diff --git a/util/sysresources/memory_unsupported.go b/util/sysresources/memory_unsupported.go index 0fde256e0..f80ef4e6e 100644 --- a/util/sysresources/memory_unsupported.go +++ b/util/sysresources/memory_unsupported.go @@ -1,8 +1,8 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -//go:build !(linux || darwin || freebsd || openbsd || dragonfly || netbsd) - -package sysresources - -func totalMemoryImpl() uint64 { return 0 } +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build !(linux || darwin || freebsd || openbsd || dragonfly || netbsd)
+
+package sysresources
+
+func totalMemoryImpl() uint64 { return 0 }
diff --git a/util/sysresources/sysresources.go b/util/sysresources/sysresources.go index 32d972ab1..1cce164a7 100644 --- a/util/sysresources/sysresources.go +++ b/util/sysresources/sysresources.go @@ -1,6 +1,6 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -// Package sysresources provides OS-independent methods of determining the -// resources available to the current system. -package sysresources +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package sysresources provides OS-independent methods of determining the
+// resources available to the current system.
+package sysresources
diff --git a/util/sysresources/sysresources_test.go b/util/sysresources/sysresources_test.go index 331ad913b..af9662042 100644 --- a/util/sysresources/sysresources_test.go +++ b/util/sysresources/sysresources_test.go @@ -1,25 +1,25 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package sysresources - -import ( - "runtime" - "testing" -) - -func TestTotalMemory(t *testing.T) { - switch runtime.GOOS { - case "linux": - case "freebsd", "openbsd", "dragonfly", "netbsd": - case "darwin": - default: - t.Skipf("not supported on runtime.GOOS=%q yet", runtime.GOOS) - } - - mem := TotalMemory() - if mem == 0 { - t.Fatal("wanted TotalMemory > 0") - } - t.Logf("total memory: %v bytes", mem) -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package sysresources
+
+import (
+ "runtime"
+ "testing"
+)
+
+func TestTotalMemory(t *testing.T) {
+ switch runtime.GOOS {
+ case "linux":
+ case "freebsd", "openbsd", "dragonfly", "netbsd":
+ case "darwin":
+ default:
+ t.Skipf("not supported on runtime.GOOS=%q yet", runtime.GOOS)
+ }
+
+ mem := TotalMemory()
+ if mem == 0 {
+ t.Fatal("wanted TotalMemory > 0")
+ }
+ t.Logf("total memory: %v bytes", mem)
+}
diff --git a/util/systemd/doc.go b/util/systemd/doc.go index 0c28e1823..296f74e9d 100644 --- a/util/systemd/doc.go +++ b/util/systemd/doc.go @@ -1,13 +1,13 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -/* -Package systemd contains a minimal wrapper around systemd-notify to enable -applications to signal readiness and status to systemd. - -This package will only have effect on Linux systems running Tailscale in a -systemd unit with the Type=notify flag set. On other operating systems (or -when running in a Linux distro without being run from inside systemd) this -package will become a no-op. -*/ -package systemd +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+/*
+Package systemd contains a minimal wrapper around systemd-notify to enable
+applications to signal readiness and status to systemd.
+
+This package will only have effect on Linux systems running Tailscale in a
+systemd unit with the Type=notify flag set. On other operating systems (or
+when running in a Linux distro without being run from inside systemd) this
+package will become a no-op.
+*/
+package systemd
diff --git a/util/systemd/systemd_linux.go b/util/systemd/systemd_linux.go index 909cfcb20..34d6daff3 100644 --- a/util/systemd/systemd_linux.go +++ b/util/systemd/systemd_linux.go @@ -1,77 +1,77 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -//go:build linux - -package systemd - -import ( - "errors" - "log" - "os" - "sync" - - "github.com/mdlayher/sdnotify" -) - -var getNotifyOnce struct { - sync.Once - v *sdnotify.Notifier -} - -type logOnce struct { - sync.Once -} - -func (l *logOnce) logf(format string, args ...any) { - l.Once.Do(func() { - log.Printf(format, args...) - }) -} - -var ( - readyOnce = &logOnce{} - statusOnce = &logOnce{} -) - -func notifier() *sdnotify.Notifier { - getNotifyOnce.Do(func() { - var err error - getNotifyOnce.v, err = sdnotify.New() - // Not exist means probably not running under systemd, so don't log. - if err != nil && !errors.Is(err, os.ErrNotExist) { - log.Printf("systemd: systemd-notifier error: %v", err) - } - }) - return getNotifyOnce.v -} - -// Ready signals readiness to systemd. This will unblock service dependents from starting. -func Ready() { - err := notifier().Notify(sdnotify.Ready) - if err != nil { - readyOnce.logf("systemd: error notifying: %v", err) - } -} - -// Status sends a single line status update to systemd so that information shows up -// in systemctl output. For example: -// -// $ systemctl status tailscale -// ● tailscale.service - Tailscale client daemon -// Loaded: loaded (/nix/store/qc312qcy907wz80fqrgbbm8a9djafmlg-unit-tailscale.service/tailscale.service; enabled; vendor preset: enabled) -// Active: active (running) since Tue 2020-11-24 17:54:07 EST; 13h ago -// Main PID: 26741 (.tailscaled-wra) -// Status: "Connected; user@host.domain.tld; 100.101.102.103" -// IP: 0B in, 0B out -// Tasks: 22 (limit: 4915) -// Memory: 30.9M -// CPU: 2min 38.469s -// CGroup: /system.slice/tailscale.service -// └─26741 /nix/store/sv6cj4mw2jajm9xkbwj07k29dj30lh0n-tailscale-date.20200727/bin/tailscaled --port 41641 -func Status(format string, args ...any) { - err := notifier().Notify(sdnotify.Statusf(format, args...)) - if err != nil { - statusOnce.logf("systemd: error notifying: %v", err) - } -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build linux
+
+package systemd
+
+import (
+ "errors"
+ "log"
+ "os"
+ "sync"
+
+ "github.com/mdlayher/sdnotify"
+)
+
+var getNotifyOnce struct {
+ sync.Once
+ v *sdnotify.Notifier
+}
+
+type logOnce struct {
+ sync.Once
+}
+
+func (l *logOnce) logf(format string, args ...any) {
+ l.Once.Do(func() {
+ log.Printf(format, args...)
+ })
+}
+
+var (
+ readyOnce = &logOnce{}
+ statusOnce = &logOnce{}
+)
+
+func notifier() *sdnotify.Notifier {
+ getNotifyOnce.Do(func() {
+ var err error
+ getNotifyOnce.v, err = sdnotify.New()
+ // Not exist means probably not running under systemd, so don't log.
+ if err != nil && !errors.Is(err, os.ErrNotExist) {
+ log.Printf("systemd: systemd-notifier error: %v", err)
+ }
+ })
+ return getNotifyOnce.v
+}
+
+// Ready signals readiness to systemd. This will unblock service dependents from starting.
+func Ready() {
+ err := notifier().Notify(sdnotify.Ready)
+ if err != nil {
+ readyOnce.logf("systemd: error notifying: %v", err)
+ }
+}
+
+// Status sends a single line status update to systemd so that information shows up
+// in systemctl output. For example:
+//
+// $ systemctl status tailscale
+// ● tailscale.service - Tailscale client daemon
+// Loaded: loaded (/nix/store/qc312qcy907wz80fqrgbbm8a9djafmlg-unit-tailscale.service/tailscale.service; enabled; vendor preset: enabled)
+// Active: active (running) since Tue 2020-11-24 17:54:07 EST; 13h ago
+// Main PID: 26741 (.tailscaled-wra)
+// Status: "Connected; user@host.domain.tld; 100.101.102.103"
+// IP: 0B in, 0B out
+// Tasks: 22 (limit: 4915)
+// Memory: 30.9M
+// CPU: 2min 38.469s
+// CGroup: /system.slice/tailscale.service
+// └─26741 /nix/store/sv6cj4mw2jajm9xkbwj07k29dj30lh0n-tailscale-date.20200727/bin/tailscaled --port 41641
+func Status(format string, args ...any) {
+ err := notifier().Notify(sdnotify.Statusf(format, args...))
+ if err != nil {
+ statusOnce.logf("systemd: error notifying: %v", err)
+ }
+}
diff --git a/util/systemd/systemd_nonlinux.go b/util/systemd/systemd_nonlinux.go index 36214020c..d8b20665f 100644 --- a/util/systemd/systemd_nonlinux.go +++ b/util/systemd/systemd_nonlinux.go @@ -1,9 +1,9 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -//go:build !linux - -package systemd - -func Ready() {} -func Status(string, ...any) {} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build !linux
+
+package systemd
+
+func Ready() {}
+func Status(string, ...any) {}
diff --git a/util/testenv/testenv.go b/util/testenv/testenv.go index 12ada9003..02c688803 100644 --- a/util/testenv/testenv.go +++ b/util/testenv/testenv.go @@ -1,21 +1,21 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -// Package testenv provides utility functions for tests. It does not depend on -// the `testing` package to allow usage in non-test code. -package testenv - -import ( - "flag" - - "tailscale.com/types/lazy" -) - -var lazyInTest lazy.SyncValue[bool] - -// InTest reports whether the current binary is a test binary. -func InTest() bool { - return lazyInTest.Get(func() bool { - return flag.Lookup("test.v") != nil - }) -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package testenv provides utility functions for tests. It does not depend on
+// the `testing` package to allow usage in non-test code.
+package testenv
+
+import (
+ "flag"
+
+ "tailscale.com/types/lazy"
+)
+
+var lazyInTest lazy.SyncValue[bool]
+
+// InTest reports whether the current binary is a test binary.
+func InTest() bool {
+ return lazyInTest.Get(func() bool {
+ return flag.Lookup("test.v") != nil
+ })
+}
diff --git a/util/truncate/truncate_test.go b/util/truncate/truncate_test.go index c0d9e6e14..6ead55a6a 100644 --- a/util/truncate/truncate_test.go +++ b/util/truncate/truncate_test.go @@ -1,36 +1,36 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package truncate_test - -import ( - "testing" - - "tailscale.com/util/truncate" -) - -func TestString(t *testing.T) { - tests := []struct { - input string - size int - want string - }{ - {"", 1000, ""}, // n > length - {"abc", 4, "abc"}, // n > length - {"abc", 3, "abc"}, // n == length - {"abcdefg", 4, "abcd"}, // n < length, safe - {"abcdefg", 0, ""}, // n < length, safe - {"abc\U0001fc2d", 3, "abc"}, // n < length, at boundary - {"abc\U0001fc2d", 4, "abc"}, // n < length, mid-rune - {"abc\U0001fc2d", 5, "abc"}, // n < length, mid-rune - {"abc\U0001fc2d", 6, "abc"}, // n < length, mid-rune - {"abc\U0001fc2defg", 7, "abc"}, // n < length, cut multibyte - } - - for _, tc := range tests { - got := truncate.String(tc.input, tc.size) - if got != tc.want { - t.Errorf("truncate(%q, %d): got %q, want %q", tc.input, tc.size, got, tc.want) - } - } -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package truncate_test
+
+import (
+ "testing"
+
+ "tailscale.com/util/truncate"
+)
+
+func TestString(t *testing.T) {
+ tests := []struct {
+ input string
+ size int
+ want string
+ }{
+ {"", 1000, ""}, // n > length
+ {"abc", 4, "abc"}, // n > length
+ {"abc", 3, "abc"}, // n == length
+ {"abcdefg", 4, "abcd"}, // n < length, safe
+ {"abcdefg", 0, ""}, // n < length, safe
+ {"abc\U0001fc2d", 3, "abc"}, // n < length, at boundary
+ {"abc\U0001fc2d", 4, "abc"}, // n < length, mid-rune
+ {"abc\U0001fc2d", 5, "abc"}, // n < length, mid-rune
+ {"abc\U0001fc2d", 6, "abc"}, // n < length, mid-rune
+ {"abc\U0001fc2defg", 7, "abc"}, // n < length, cut multibyte
+ }
+
+ for _, tc := range tests {
+ got := truncate.String(tc.input, tc.size)
+ if got != tc.want {
+ t.Errorf("truncate(%q, %d): got %q, want %q", tc.input, tc.size, got, tc.want)
+ }
+ }
+}
diff --git a/util/uniq/slice.go b/util/uniq/slice.go index 4ab933a9d..fb46cc491 100644 --- a/util/uniq/slice.go +++ b/util/uniq/slice.go @@ -1,62 +1,62 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -// Package uniq provides removal of adjacent duplicate elements in slices. -// It is similar to the unix command uniq. -package uniq - -// ModifySlice removes adjacent duplicate elements from the given slice. It -// adjusts the length of the slice appropriately and zeros the tail. -// -// ModifySlice does O(len(*slice)) operations. -func ModifySlice[E comparable](slice *[]E) { - // Remove duplicates - dst := 0 - for i := 1; i < len(*slice); i++ { - if (*slice)[i] == (*slice)[dst] { - continue - } - dst++ - (*slice)[dst] = (*slice)[i] - } - - // Zero out the elements we removed at the end of the slice - end := dst + 1 - var zero E - for i := end; i < len(*slice); i++ { - (*slice)[i] = zero - } - - // Truncate the slice - if end < len(*slice) { - *slice = (*slice)[:end] - } -} - -// ModifySliceFunc is the same as ModifySlice except that it allows using a -// custom comparison function. -// -// eq should report whether the two provided elements are equal. -func ModifySliceFunc[E any](slice *[]E, eq func(i, j E) bool) { - // Remove duplicates - dst := 0 - for i := 1; i < len(*slice); i++ { - if eq((*slice)[dst], (*slice)[i]) { - continue - } - dst++ - (*slice)[dst] = (*slice)[i] - } - - // Zero out the elements we removed at the end of the slice - end := dst + 1 - var zero E - for i := end; i < len(*slice); i++ { - (*slice)[i] = zero - } - - // Truncate the slice - if end < len(*slice) { - *slice = (*slice)[:end] - } -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package uniq provides removal of adjacent duplicate elements in slices.
+// It is similar to the unix command uniq.
+package uniq
+
+// ModifySlice removes adjacent duplicate elements from the given slice. It
+// adjusts the length of the slice appropriately and zeros the tail.
+//
+// ModifySlice does O(len(*slice)) operations.
+func ModifySlice[E comparable](slice *[]E) {
+ // Remove duplicates
+ dst := 0
+ for i := 1; i < len(*slice); i++ {
+ if (*slice)[i] == (*slice)[dst] {
+ continue
+ }
+ dst++
+ (*slice)[dst] = (*slice)[i]
+ }
+
+ // Zero out the elements we removed at the end of the slice
+ end := dst + 1
+ var zero E
+ for i := end; i < len(*slice); i++ {
+ (*slice)[i] = zero
+ }
+
+ // Truncate the slice
+ if end < len(*slice) {
+ *slice = (*slice)[:end]
+ }
+}
+
+// ModifySliceFunc is the same as ModifySlice except that it allows using a
+// custom comparison function.
+//
+// eq should report whether the two provided elements are equal.
+func ModifySliceFunc[E any](slice *[]E, eq func(i, j E) bool) {
+ // Remove duplicates
+ dst := 0
+ for i := 1; i < len(*slice); i++ {
+ if eq((*slice)[dst], (*slice)[i]) {
+ continue
+ }
+ dst++
+ (*slice)[dst] = (*slice)[i]
+ }
+
+ // Zero out the elements we removed at the end of the slice
+ end := dst + 1
+ var zero E
+ for i := end; i < len(*slice); i++ {
+ (*slice)[i] = zero
+ }
+
+ // Truncate the slice
+ if end < len(*slice) {
+ *slice = (*slice)[:end]
+ }
+}
diff --git a/util/winutil/authenticode/mksyscall.go b/util/winutil/authenticode/mksyscall.go index 8b7cabe6e..7c6b33973 100644 --- a/util/winutil/authenticode/mksyscall.go +++ b/util/winutil/authenticode/mksyscall.go @@ -1,18 +1,18 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package authenticode - -//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go mksyscall.go -//go:generate go run golang.org/x/tools/cmd/goimports -w zsyscall_windows.go - -//sys cryptCATAdminAcquireContext2(hCatAdmin *_HCATADMIN, pgSubsystem *windows.GUID, hashAlgorithm *uint16, strongHashPolicy *windows.CertStrongSignPara, flags uint32) (err error) [int32(failretval)==0] = wintrust.CryptCATAdminAcquireContext2 -//sys cryptCATAdminCalcHashFromFileHandle2(hCatAdmin _HCATADMIN, file windows.Handle, pcbHash *uint32, pbHash *byte, flags uint32) (err error) [int32(failretval)==0] = wintrust.CryptCATAdminCalcHashFromFileHandle2 -//sys cryptCATAdminCatalogInfoFromContext(hCatInfo _HCATINFO, catInfo *_CATALOG_INFO, flags uint32) (err error) [int32(failretval)==0] = wintrust.CryptCATCatalogInfoFromContext -//sys cryptCATAdminEnumCatalogFromHash(hCatAdmin _HCATADMIN, pbHash *byte, cbHash uint32, flags uint32, prevCatInfo *_HCATINFO) (ret _HCATINFO, err error) [ret==0] = wintrust.CryptCATAdminEnumCatalogFromHash -//sys cryptCATAdminReleaseCatalogContext(hCatAdmin _HCATADMIN, hCatInfo _HCATINFO, flags uint32) (err error) [int32(failretval)==0] = wintrust.CryptCATAdminReleaseCatalogContext -//sys cryptCATAdminReleaseContext(hCatAdmin _HCATADMIN, flags uint32) (err error) [int32(failretval)==0] = wintrust.CryptCATAdminReleaseContext -//sys cryptMsgClose(cryptMsg windows.Handle) (err error) [int32(failretval)==0] = crypt32.CryptMsgClose -//sys cryptMsgGetParam(cryptMsg windows.Handle, paramType uint32, index uint32, data unsafe.Pointer, dataLen *uint32) (err error) [int32(failretval)==0] = crypt32.CryptMsgGetParam -//sys cryptVerifyMessageSignature(pVerifyPara *_CRYPT_VERIFY_MESSAGE_PARA, signerIndex uint32, pbSignedBlob *byte, cbSignedBlob uint32, pbDecoded *byte, pdbDecoded *uint32, ppSignerCert **windows.CertContext) (err error) [int32(failretval)==0] = crypt32.CryptVerifyMessageSignature -//sys msiGetFileSignatureInformation(signedObjectPath *uint16, flags uint32, certCtx **windows.CertContext, pbHashData *byte, cbHashData *uint32) (ret wingoes.HRESULT) = msi.MsiGetFileSignatureInformationW +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package authenticode
+
+//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go mksyscall.go
+//go:generate go run golang.org/x/tools/cmd/goimports -w zsyscall_windows.go
+
+//sys cryptCATAdminAcquireContext2(hCatAdmin *_HCATADMIN, pgSubsystem *windows.GUID, hashAlgorithm *uint16, strongHashPolicy *windows.CertStrongSignPara, flags uint32) (err error) [int32(failretval)==0] = wintrust.CryptCATAdminAcquireContext2
+//sys cryptCATAdminCalcHashFromFileHandle2(hCatAdmin _HCATADMIN, file windows.Handle, pcbHash *uint32, pbHash *byte, flags uint32) (err error) [int32(failretval)==0] = wintrust.CryptCATAdminCalcHashFromFileHandle2
+//sys cryptCATAdminCatalogInfoFromContext(hCatInfo _HCATINFO, catInfo *_CATALOG_INFO, flags uint32) (err error) [int32(failretval)==0] = wintrust.CryptCATCatalogInfoFromContext
+//sys cryptCATAdminEnumCatalogFromHash(hCatAdmin _HCATADMIN, pbHash *byte, cbHash uint32, flags uint32, prevCatInfo *_HCATINFO) (ret _HCATINFO, err error) [ret==0] = wintrust.CryptCATAdminEnumCatalogFromHash
+//sys cryptCATAdminReleaseCatalogContext(hCatAdmin _HCATADMIN, hCatInfo _HCATINFO, flags uint32) (err error) [int32(failretval)==0] = wintrust.CryptCATAdminReleaseCatalogContext
+//sys cryptCATAdminReleaseContext(hCatAdmin _HCATADMIN, flags uint32) (err error) [int32(failretval)==0] = wintrust.CryptCATAdminReleaseContext
+//sys cryptMsgClose(cryptMsg windows.Handle) (err error) [int32(failretval)==0] = crypt32.CryptMsgClose
+//sys cryptMsgGetParam(cryptMsg windows.Handle, paramType uint32, index uint32, data unsafe.Pointer, dataLen *uint32) (err error) [int32(failretval)==0] = crypt32.CryptMsgGetParam
+//sys cryptVerifyMessageSignature(pVerifyPara *_CRYPT_VERIFY_MESSAGE_PARA, signerIndex uint32, pbSignedBlob *byte, cbSignedBlob uint32, pbDecoded *byte, pdbDecoded *uint32, ppSignerCert **windows.CertContext) (err error) [int32(failretval)==0] = crypt32.CryptVerifyMessageSignature
+//sys msiGetFileSignatureInformation(signedObjectPath *uint16, flags uint32, certCtx **windows.CertContext, pbHashData *byte, cbHashData *uint32) (ret wingoes.HRESULT) = msi.MsiGetFileSignatureInformationW
diff --git a/util/winutil/policy/policy_windows.go b/util/winutil/policy/policy_windows.go index 89142951f..4674696fa 100644 --- a/util/winutil/policy/policy_windows.go +++ b/util/winutil/policy/policy_windows.go @@ -1,155 +1,155 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -// Package policy contains higher-level abstractions for accessing Windows enterprise policies. -package policy - -import ( - "time" - - "tailscale.com/util/winutil" -) - -// PreferenceOptionPolicy is a policy that governs whether a boolean variable -// is forcibly assigned an administrator-defined value, or allowed to receive -// a user-defined value. -type PreferenceOptionPolicy int - -const ( - showChoiceByPolicy PreferenceOptionPolicy = iota - neverByPolicy - alwaysByPolicy -) - -// Show returns if the UI option that controls the choice administered by this -// policy should be shown. Currently this is true if and only if the policy is -// showChoiceByPolicy. -func (p PreferenceOptionPolicy) Show() bool { - return p == showChoiceByPolicy -} - -// ShouldEnable checks if the choice administered by this policy should be -// enabled. If the administrator has chosen a setting, the administrator's -// setting is returned, otherwise userChoice is returned. -func (p PreferenceOptionPolicy) ShouldEnable(userChoice bool) bool { - switch p { - case neverByPolicy: - return false - case alwaysByPolicy: - return true - default: - return userChoice - } -} - -// GetPreferenceOptionPolicy loads a policy from the registry that can be -// managed by an enterprise policy management system and allows administrative -// overrides of users' choices in a way that we do not want tailcontrol to have -// the authority to set. It describes user-decides/always/never options, where -// "always" and "never" remove the user's ability to make a selection. If not -// present or set to a different value, "user-decides" is the default. -func GetPreferenceOptionPolicy(name string) PreferenceOptionPolicy { - opt, err := winutil.GetPolicyString(name) - if opt == "" || err != nil { - return showChoiceByPolicy - } - switch opt { - case "always": - return alwaysByPolicy - case "never": - return neverByPolicy - default: - return showChoiceByPolicy - } -} - -// VisibilityPolicy is a policy that controls whether or not a particular -// component of a user interface is to be shown. -type VisibilityPolicy byte - -const ( - visibleByPolicy VisibilityPolicy = 'v' - hiddenByPolicy VisibilityPolicy = 'h' -) - -// Show reports whether the UI option administered by this policy should be shown. -// Currently this is true if and only if the policy is visibleByPolicy. -func (p VisibilityPolicy) Show() bool { - return p == visibleByPolicy -} - -// GetVisibilityPolicy loads a policy from the registry that can be managed -// by an enterprise policy management system and describes show/hide decisions -// for UI elements. The registry value should be a string set to "show" (return -// true) or "hide" (return true). If not present or set to a different value, -// "show" (return false) is the default. -func GetVisibilityPolicy(name string) VisibilityPolicy { - opt, err := winutil.GetPolicyString(name) - if opt == "" || err != nil { - return visibleByPolicy - } - switch opt { - case "hide": - return hiddenByPolicy - default: - return visibleByPolicy - } -} - -// GetDurationPolicy loads a policy from the registry that can be managed -// by an enterprise policy management system and describes a duration for some -// action. The registry value should be a string that time.ParseDuration -// understands. If the registry value is "" or can not be processed, -// defaultValue is returned instead. -func GetDurationPolicy(name string, defaultValue time.Duration) time.Duration { - opt, err := winutil.GetPolicyString(name) - if opt == "" || err != nil { - return defaultValue - } - v, err := time.ParseDuration(opt) - if err != nil || v < 0 { - return defaultValue - } - return v -} - -// SelectControlURL returns the ControlURL to use based on a value in -// the registry (LoginURL) and the one on disk (in the GUI's -// prefs.conf). If both are empty, it returns a default value. (It -// always return a non-empty value) -// -// See https://github.com/tailscale/tailscale/issues/2798 for some background. -func SelectControlURL(reg, disk string) string { - const def = "https://controlplane.tailscale.com" - - // Prior to Dec 2020's commit 739b02e6, the installer - // wrote a LoginURL value of https://login.tailscale.com to the registry. - const oldRegDef = "https://login.tailscale.com" - - // If they have an explicit value in the registry, use it, - // unless it's an old default value from an old installer. - // Then we have to see which is better. - if reg != "" { - if reg != oldRegDef { - // Something explicit in the registry that we didn't - // set ourselves by the installer. - return reg - } - if disk == "" { - // Something in the registry is better than nothing on disk. - return reg - } - if disk != def && disk != oldRegDef { - // The value in the registry is the old - // default (login.tailscale.com) but the value - // on disk is neither our old nor new default - // value, so it must be some custom thing that - // the user cares about. Prefer the disk value. - return disk - } - } - if disk != "" { - return disk - } - return def -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package policy contains higher-level abstractions for accessing Windows enterprise policies.
+package policy
+
+import (
+ "time"
+
+ "tailscale.com/util/winutil"
+)
+
+// PreferenceOptionPolicy is a policy that governs whether a boolean variable
+// is forcibly assigned an administrator-defined value, or allowed to receive
+// a user-defined value.
+type PreferenceOptionPolicy int
+
+const (
+ showChoiceByPolicy PreferenceOptionPolicy = iota
+ neverByPolicy
+ alwaysByPolicy
+)
+
+// Show returns if the UI option that controls the choice administered by this
+// policy should be shown. Currently this is true if and only if the policy is
+// showChoiceByPolicy.
+func (p PreferenceOptionPolicy) Show() bool {
+ return p == showChoiceByPolicy
+}
+
+// ShouldEnable checks if the choice administered by this policy should be
+// enabled. If the administrator has chosen a setting, the administrator's
+// setting is returned, otherwise userChoice is returned.
+func (p PreferenceOptionPolicy) ShouldEnable(userChoice bool) bool {
+ switch p {
+ case neverByPolicy:
+ return false
+ case alwaysByPolicy:
+ return true
+ default:
+ return userChoice
+ }
+}
+
+// GetPreferenceOptionPolicy loads a policy from the registry that can be
+// managed by an enterprise policy management system and allows administrative
+// overrides of users' choices in a way that we do not want tailcontrol to have
+// the authority to set. It describes user-decides/always/never options, where
+// "always" and "never" remove the user's ability to make a selection. If not
+// present or set to a different value, "user-decides" is the default.
+func GetPreferenceOptionPolicy(name string) PreferenceOptionPolicy {
+ opt, err := winutil.GetPolicyString(name)
+ if opt == "" || err != nil {
+ return showChoiceByPolicy
+ }
+ switch opt {
+ case "always":
+ return alwaysByPolicy
+ case "never":
+ return neverByPolicy
+ default:
+ return showChoiceByPolicy
+ }
+}
+
+// VisibilityPolicy is a policy that controls whether or not a particular
+// component of a user interface is to be shown.
+type VisibilityPolicy byte
+
+const (
+ visibleByPolicy VisibilityPolicy = 'v'
+ hiddenByPolicy VisibilityPolicy = 'h'
+)
+
+// Show reports whether the UI option administered by this policy should be shown.
+// Currently this is true if and only if the policy is visibleByPolicy.
+func (p VisibilityPolicy) Show() bool {
+ return p == visibleByPolicy
+}
+
+// GetVisibilityPolicy loads a policy from the registry that can be managed
+// by an enterprise policy management system and describes show/hide decisions
+// for UI elements. The registry value should be a string set to "show" (return
+// true) or "hide" (return true). If not present or set to a different value,
+// "show" (return false) is the default.
+func GetVisibilityPolicy(name string) VisibilityPolicy {
+ opt, err := winutil.GetPolicyString(name)
+ if opt == "" || err != nil {
+ return visibleByPolicy
+ }
+ switch opt {
+ case "hide":
+ return hiddenByPolicy
+ default:
+ return visibleByPolicy
+ }
+}
+
+// GetDurationPolicy loads a policy from the registry that can be managed
+// by an enterprise policy management system and describes a duration for some
+// action. The registry value should be a string that time.ParseDuration
+// understands. If the registry value is "" or can not be processed,
+// defaultValue is returned instead.
+func GetDurationPolicy(name string, defaultValue time.Duration) time.Duration {
+ opt, err := winutil.GetPolicyString(name)
+ if opt == "" || err != nil {
+ return defaultValue
+ }
+ v, err := time.ParseDuration(opt)
+ if err != nil || v < 0 {
+ return defaultValue
+ }
+ return v
+}
+
+// SelectControlURL returns the ControlURL to use based on a value in
+// the registry (LoginURL) and the one on disk (in the GUI's
+// prefs.conf). If both are empty, it returns a default value. (It
+// always return a non-empty value)
+//
+// See https://github.com/tailscale/tailscale/issues/2798 for some background.
+func SelectControlURL(reg, disk string) string {
+ const def = "https://controlplane.tailscale.com"
+
+ // Prior to Dec 2020's commit 739b02e6, the installer
+ // wrote a LoginURL value of https://login.tailscale.com to the registry.
+ const oldRegDef = "https://login.tailscale.com"
+
+ // If they have an explicit value in the registry, use it,
+ // unless it's an old default value from an old installer.
+ // Then we have to see which is better.
+ if reg != "" {
+ if reg != oldRegDef {
+ // Something explicit in the registry that we didn't
+ // set ourselves by the installer.
+ return reg
+ }
+ if disk == "" {
+ // Something in the registry is better than nothing on disk.
+ return reg
+ }
+ if disk != def && disk != oldRegDef {
+ // The value in the registry is the old
+ // default (login.tailscale.com) but the value
+ // on disk is neither our old nor new default
+ // value, so it must be some custom thing that
+ // the user cares about. Prefer the disk value.
+ return disk
+ }
+ }
+ if disk != "" {
+ return disk
+ }
+ return def
+}
diff --git a/util/winutil/policy/policy_windows_test.go b/util/winutil/policy/policy_windows_test.go index cf2390c56..ebfd185de 100644 --- a/util/winutil/policy/policy_windows_test.go +++ b/util/winutil/policy/policy_windows_test.go @@ -1,38 +1,38 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package policy - -import "testing" - -func TestSelectControlURL(t *testing.T) { - tests := []struct { - reg, disk, want string - }{ - // Modern default case. - {"", "", "https://controlplane.tailscale.com"}, - - // For a user who installed prior to Dec 2020, with - // stuff in their registry. - {"https://login.tailscale.com", "", "https://login.tailscale.com"}, - - // Ignore pre-Dec'20 LoginURL from installer if prefs - // prefs overridden manually to an on-prem control - // server. - {"https://login.tailscale.com", "http://on-prem", "http://on-prem"}, - - // Something unknown explicitly set in the registry always wins. - {"http://explicit-reg", "", "http://explicit-reg"}, - {"http://explicit-reg", "http://on-prem", "http://explicit-reg"}, - {"http://explicit-reg", "https://login.tailscale.com", "http://explicit-reg"}, - {"http://explicit-reg", "https://controlplane.tailscale.com", "http://explicit-reg"}, - - // If nothing in the registry, disk wins. - {"", "http://on-prem", "http://on-prem"}, - } - for _, tt := range tests { - if got := SelectControlURL(tt.reg, tt.disk); got != tt.want { - t.Errorf("(reg %q, disk %q) = %q; want %q", tt.reg, tt.disk, got, tt.want) - } - } -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package policy
+
+import "testing"
+
+func TestSelectControlURL(t *testing.T) {
+ tests := []struct {
+ reg, disk, want string
+ }{
+ // Modern default case.
+ {"", "", "https://controlplane.tailscale.com"},
+
+ // For a user who installed prior to Dec 2020, with
+ // stuff in their registry.
+ {"https://login.tailscale.com", "", "https://login.tailscale.com"},
+
+ // Ignore pre-Dec'20 LoginURL from installer if prefs
+ // prefs overridden manually to an on-prem control
+ // server.
+ {"https://login.tailscale.com", "http://on-prem", "http://on-prem"},
+
+ // Something unknown explicitly set in the registry always wins.
+ {"http://explicit-reg", "", "http://explicit-reg"},
+ {"http://explicit-reg", "http://on-prem", "http://explicit-reg"},
+ {"http://explicit-reg", "https://login.tailscale.com", "http://explicit-reg"},
+ {"http://explicit-reg", "https://controlplane.tailscale.com", "http://explicit-reg"},
+
+ // If nothing in the registry, disk wins.
+ {"", "http://on-prem", "http://on-prem"},
+ }
+ for _, tt := range tests {
+ if got := SelectControlURL(tt.reg, tt.disk); got != tt.want {
+ t.Errorf("(reg %q, disk %q) = %q; want %q", tt.reg, tt.disk, got, tt.want)
+ }
+ }
+}
|
