Compare commits

...
23 Commits
Author SHA1 Message Date
moloch-- f24c057503 Implement --env and --env-inheritance for 'execute' to allow greater control over subprocess env 2026-01-13 20:55:20 -08:00
moloch-- 7e3c1f1549 v1.6.3 2026-01-13 16:28:27 -08:00
Joe 1d4c0ec8a5 Merge pull request #2106 from cmprmsd/feature/extension-alias-autocompletion
Add argument completion for aliases and extensions
2026-01-13 17:25:11 -07:00
Joe 92dda6aa4f Merge pull request #2112 from BishopFox/6b37dependabot/go_modules/google.golang.org/protobuf-1.36.11
Bump google.golang.org/protobuf from 1.36.10 to 1.36.11
2026-01-13 17:24:30 -07:00
Joe 9a605dfda6 Merge pull request #2113 from BishopFox/0f1edependabot/go_modules/golang.org/x/sys-0.40.0
Bump golang.org/x/sys from 0.39.0 to 0.40.0
2026-01-13 17:24:14 -07:00
Joe e918885556 Merge pull request #2114 from Gubarz/getsystem
Fix: getsystem shellcode generation
2026-01-13 17:24:00 -07:00
Joe 263702594f Merge pull request #2115 from Gubarz/execute-assembly
Revert go-donut from 2022 patch to 2021
2026-01-13 17:23:17 -07:00
Joe 72b8dfb454 Merge pull request #2116 from BishopFox/fix/armory-all
Fix armory install all cmd
2026-01-13 17:23:00 -07:00
moloch-- aa2383ac24 Fix armory install all cmd 2026-01-12 21:45:21 -08:00
dependabot[bot] b6f0ea013d Bump golang.org/x/sys from 0.39.0 to 0.40.0
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.39.0 to 0.40.0.
- [Commits](https://github.com/golang/sys/compare/v0.39.0...v0.40.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-version: 0.40.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-01-13 05:34:38 +00:00
dependabot[bot] eb82e18a52 Bump google.golang.org/protobuf from 1.36.10 to 1.36.11
Bumps google.golang.org/protobuf from 1.36.10 to 1.36.11.

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  dependency-version: 1.36.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-01-13 05:34:34 +00:00
Joe 40ea51b685 Merge pull request #2111 from BishopFox/3a38dependabot/go_modules/golang.org/x/net-0.48.0
Bump golang.org/x/net from 0.47.0 to 0.48.0
2026-01-12 22:33:30 -07:00
Joe 1285b44c50 Merge pull request #2110 from BishopFox/d73edependabot/go_modules/github.com/miekg/dns-1.1.70
Bump github.com/miekg/dns from 1.1.69 to 1.1.70
2026-01-12 22:33:18 -07:00
Joe cb63a3378f Merge pull request #2109 from BishopFox/dca1dependabot/go_modules/tailscale.com-1.92.5
Bump tailscale.com from 1.90.6 to 1.92.5
2026-01-12 22:33:02 -07:00
Gubarz 4f90bfdcda revert go-donut from 2022 to 2021 2026-01-12 13:48:53 -07:00
Gubarz 5bb1dcc137 Fix getsystem rpc errors 2026-01-12 12:25:00 -07:00
dependabot[bot] 57c417c48f Bump golang.org/x/net from 0.47.0 to 0.48.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.47.0 to 0.48.0.
- [Commits](https://github.com/golang/net/compare/v0.47.0...v0.48.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.48.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-01-12 03:05:52 +00:00
dependabot[bot] 55fc59f6f5 Bump github.com/miekg/dns from 1.1.69 to 1.1.70
Bumps [github.com/miekg/dns](https://github.com/miekg/dns) from 1.1.69 to 1.1.70.
- [Commits](https://github.com/miekg/dns/compare/v1.1.69...v1.1.70)

---
updated-dependencies:
- dependency-name: github.com/miekg/dns
  dependency-version: 1.1.70
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-01-12 03:05:44 +00:00
dependabot[bot] 332852d095 Bump tailscale.com from 1.90.6 to 1.92.5
Bumps [tailscale.com](https://github.com/tailscale/tailscale) from 1.90.6 to 1.92.5.
- [Release notes](https://github.com/tailscale/tailscale/releases)
- [Commits](https://github.com/tailscale/tailscale/compare/v1.90.6...v1.92.5)

---
updated-dependencies:
- dependency-name: tailscale.com
  dependency-version: 1.92.5
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-01-12 03:05:34 +00:00
cmprmsd a903d2f596 Add support for predefined argument completion choices. in extensions and aliases
Example:
{
      "name": "browser",
      "desc": "target browser (chrome, edge, brave, all)",
      "type": "string",
      "optional": true,
      "choices": ["chrome", "edge", "brave", "all"]
    },
2026-01-11 22:11:40 +01:00
cmprmsd 4a4bb683e3 Add argument completion for aliases and extensions
- Introduced `AliasArgument` struct to define arguments for alias commands.
- Enhanced `LoadAlias` function to build usage and help messages including argument details.
- Implemented argument completion for alias commands using Carapace.
- Updated `makeAliasArgCompleter` to support positional and flag-style argument completions.
- Refactored `makeExtensionArgCompleter` to include similar completion features for extension commands.
- Improved user experience by displaying argument descriptions in completion lists one argument per line (instead of grouped).
2026-01-09 15:46:32 +01:00
moloch-- 016299c756 v1.6.2 2026-01-08 12:56:41 -08:00
Joe 075caaccda Merge pull request #2104 from BishopFox/rc/flush
Flush output on exit
2026-01-08 13:54:54 -07:00
218 changed files with 10515 additions and 7517 deletions

No files matched your search

+168 -5
View File
@@ -36,6 +36,7 @@ import (
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/bishopfox/sliver/util"
"github.com/carapace-sh/carapace"
app "github.com/reeflective/console"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@@ -76,6 +77,16 @@ type AliasFile struct {
Path string `json:"path"`
}
// AliasArgument - An argument for an alias command.
type AliasArgument struct {
Name string `json:"name"`
Type string `json:"type"`
Desc string `json:"desc"`
Optional bool `json:"optional"`
Default interface{} `json:"default,omitempty"`
Choices []string `json:"choices,omitempty"`
}
// AliasManifest - The manifest for an alias, contains metadata.
type AliasManifest struct {
Name string `json:"name"`
@@ -89,6 +100,7 @@ type AliasManifest struct {
Entrypoint string `json:"entrypoint"`
AllowArgs bool `json:"allow_args"`
DefaultArgs string `json:"default_args"`
Arguments []*AliasArgument `json:"arguments"`
Files []*AliasFile `json:"files"`
IsReflective bool `json:"is_reflective"`
IsAssembly bool `json:"is_assembly"`
@@ -158,15 +170,60 @@ func LoadAlias(manifestPath string, cmd *cobra.Command, con *console.SliverClien
}
aliasManifest.RootPath = filepath.Dir(manifestPath)
// Build usage string including arguments
usage := strings.Builder{}
usage.WriteString(aliasManifest.CommandName)
for _, arg := range aliasManifest.Arguments {
usage.WriteString(" ")
if arg.Optional {
usage.WriteString("[")
}
usage.WriteString(strings.ToUpper(arg.Name))
if arg.Optional {
usage.WriteString("]")
}
}
// Build long help message
longHelp := strings.Builder{}
longHelp.WriteString("[[.Bold]]Command:[[.Normal]]")
longHelp.WriteString(usage.String())
longHelp.WriteString("\n")
if len(aliasManifest.Help) > 0 || len(aliasManifest.LongHelp) > 0 {
longHelp.WriteString("[[.Bold]]About:[[.Normal]]")
if len(aliasManifest.Help) > 0 {
longHelp.WriteString(aliasManifest.Help)
longHelp.WriteString("\n")
}
if len(aliasManifest.LongHelp) > 0 {
longHelp.WriteString(aliasManifest.LongHelp)
longHelp.WriteString("\n")
}
}
if len(aliasManifest.Arguments) > 0 {
longHelp.WriteString("[[.Bold]]Arguments:[[.Normal]]")
for _, arg := range aliasManifest.Arguments {
longHelp.WriteString("\n\t")
optStr := ""
if arg.Optional {
optStr = "[OPTIONAL]"
}
aType := arg.Type
if aType == "wstring" {
aType = "string"
}
longHelp.WriteString(fmt.Sprintf("%s (%s):\t%s%s", strings.ToUpper(arg.Name), aType, optStr, arg.Desc))
}
}
longHelp.WriteString("\n\n⚠️ If you're having issues passing arguments to the alias please read:\n")
longHelp.WriteString("https://github.com/BishopFox/sliver/wiki/Aliases-&-Extensions#aliases-command-parsing")
// for each alias command, add a new app command
helpMsg := fmt.Sprintf("[%s] %s", aliasManifest.Name, aliasManifest.Help)
longHelpMsg := help.FormatHelpTmpl(aliasManifest.LongHelp)
longHelpMsg += "\n\n⚠️ If you're having issues passing arguments to the alias please read:\n"
longHelpMsg += "https://github.com/BishopFox/sliver/wiki/Aliases-&-Extensions#aliases-command-parsing"
addAliasCmd := &cobra.Command{
Use: aliasManifest.CommandName,
Use: usage.String(),
Short: helpMsg,
Long: longHelpMsg,
Long: help.FormatHelpTmpl(longHelp.String()),
Run: func(cmd *cobra.Command, args []string) {
runAliasCommand(cmd, con, args)
},
@@ -196,6 +253,10 @@ func LoadAlias(manifestPath string, cmd *cobra.Command, con *console.SliverClien
f.IntP("timeout", "t", defaultTimeout, "command timeout in seconds")
addAliasCmd.Flags().AddFlagSet(f)
// Setup completions for alias arguments
comps := carapace.Gen(addAliasCmd)
makeAliasArgCompleter(aliasManifest, comps)
cmd.AddCommand(addAliasCmd)
// Have to use a global map here, as passing the aliasCmd
@@ -589,3 +650,105 @@ func makeAliasPlatformFilters(alias *AliasManifest) map[string]string {
app.CommandFilterKey: strings.Join(all, ","),
}
}
// makeAliasArgCompleter builds the positional and dash arguments completer for the alias.
// It provides completion for:
// 1. Positional arguments (before --)
// 2. Flag-style arguments after -- (e.g., --target, --port)
func makeAliasArgCompleter(alias *AliasManifest, comps *carapace.Carapace) {
if len(alias.Arguments) == 0 {
return
}
var actions []carapace.Action
for _, arg := range alias.Arguments {
var action carapace.Action
// If choices are defined, use them for completion
if len(arg.Choices) > 0 {
action = carapace.ActionValues(arg.Choices...).Tag("choices")
} else {
// Fall back to type-based completion
switch arg.Type {
case "file":
action = carapace.ActionFiles().Tag("alias data")
default:
action = carapace.ActionValues()
}
}
usage := fmt.Sprintf("(%s) %s", arg.Type, arg.Desc)
if arg.Optional {
usage += " (optional)"
}
actions = append(actions, action.Usage("%s", usage))
}
comps.PositionalCompletion(actions...)
// Add dash completion for flag-style arguments after --
// Pre-build the flag completions at registration time (not in a callback)
flagCompletion := makeAliasFlagNameCompletion(alias)
// Build value completions for each argument type
valueCompletions := make(map[string]carapace.Action)
for _, arg := range alias.Arguments {
// If choices are defined, use them for completion
if len(arg.Choices) > 0 {
valueCompletions[arg.Name] = carapace.ActionValues(arg.Choices...).Tag("choices")
} else {
// Fall back to type-based completion
switch arg.Type {
case "file":
valueCompletions[arg.Name] = carapace.ActionFiles().Tag("file path")
case "bool":
valueCompletions[arg.Name] = carapace.ActionValues("true", "false").Tag("boolean")
default:
valueCompletions[arg.Name] = carapace.ActionValues()
}
}
}
// Use DashAnyCompletion with a smart action that determines context
comps.DashAnyCompletion(
carapace.ActionCallback(func(c carapace.Context) carapace.Action {
// If typing a flag (starts with -)
if strings.HasPrefix(c.Value, "-") {
return flagCompletion
}
// If previous arg was a flag, complete its value
if len(c.Args) > 0 {
lastArg := c.Args[len(c.Args)-1]
if strings.HasPrefix(lastArg, "-") {
flagName := strings.TrimLeft(lastArg, "-")
if action, ok := valueCompletions[flagName]; ok {
return action
}
}
}
// Default: show flag names
return flagCompletion
}),
)
}
// makeAliasFlagNameCompletion creates completion for flag names
func makeAliasFlagNameCompletion(alias *AliasManifest) carapace.Action {
var results []string
for _, arg := range alias.Arguments {
flagName := fmt.Sprintf("--%s", arg.Name)
desc := arg.Desc
if arg.Optional {
desc = fmt.Sprintf("[optional] %s", desc)
}
desc = fmt.Sprintf("(%s) %s", arg.Type, desc)
results = append(results, flagName, desc)
}
return carapace.ActionValuesDescribed(results...).Tag("alias arguments")
}
+16 -4
View File
@@ -274,19 +274,22 @@ func getInstalledPackageNames() []string {
}
// This is a convenience function to get the names of the commands in the cache
func getCommandsInCache() []string {
func getCommandsInCache(armoryPK string) []string {
commandNames := []string{}
pkgCache.Range(func(key, value interface{}) bool {
cacheEntry := value.(pkgCacheEntry)
if cacheEntry.LastErr == nil {
if armoryPK != "" && cacheEntry.ArmoryConfig.PublicKey != armoryPK {
return true
}
if cacheEntry.Pkg.IsAlias {
if !slices.Contains(commandNames, cacheEntry.Alias.CommandName) {
if cacheEntry.Alias.CommandName != "" && !slices.Contains(commandNames, cacheEntry.Alias.CommandName) {
commandNames = append(commandNames, cacheEntry.Alias.CommandName)
}
} else {
for _, command := range cacheEntry.Extension.ExtCommand {
if !slices.Contains(commandNames, command.CommandName) {
if command.CommandName != "" && !slices.Contains(commandNames, command.CommandName) {
commandNames = append(commandNames, command.CommandName)
}
}
@@ -398,7 +401,7 @@ func buildInstallList(name, armoryPK string, forceInstallation bool, pendingPack
var requestedPackageList []string
if name == "all" {
requestedPackageList = []string{}
allCommands := getCommandsInCache()
allCommands := getCommandsInCache(armoryPK)
for _, cmdName := range allCommands {
if !slices.Contains(installedPackages, cmdName) || forceInstallation {
// Check to see if there is a package pending with that name
@@ -458,6 +461,15 @@ func installPackageByName(name, armoryPK string, forceInstallation, promptToOver
if err != nil {
return err
}
if len(packageInstallList) == 0 && name == "all" {
availableCommands := getCommandsInCache(armoryPK)
if len(availableCommands) == 0 {
con.PrintInfof("No packages or bundles found\n")
} else {
con.PrintInfof("All available packages are already installed\n")
}
return nil
}
if len(packageInstallList) > 0 {
for _, packageID := range packageInstallList {
entry := packageCacheLookupByID(packageID)
+3 -1
View File
@@ -25,12 +25,14 @@ func Commands(con *console.SliverClient) []*cobra.Command {
}
flags.Bind("", false, executeCmd, func(f *pflag.FlagSet) {
f.BoolP("token", "T", false, "execute command with current token (Windows only)")
f.BoolP("output", "o", false, "capture command output")
f.BoolP("output", "o", true, "capture command output")
f.BoolP("save", "s", false, "save output to a file")
f.BoolP("loot", "X", false, "save output as loot")
f.BoolP("ignore-stderr", "S", false, "don't print STDERR output")
f.StringP("stdout", "O", "", "remote path to redirect STDOUT to")
f.StringP("stderr", "E", "", "remote path to redirect STDERR to")
f.Bool("env-inheritance", true, "inherit environment variables from the current process")
f.StringArray("env", nil, "set an environment variable for the child process (key=value)")
f.StringP("name", "n", "", "name to assign loot (optional)")
f.Uint32P("ppid", "P", 0, "parent process id (optional, Windows only)")
f.BoolP("hidden", "H", false, "hide the window of the spawned process (Windows only)")
+35 -7
View File
@@ -49,6 +49,13 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
saveLoot, _ := cmd.Flags().GetBool("loot")
saveOutput, _ := cmd.Flags().GetBool("save")
ppid, _ := cmd.Flags().GetUint32("ppid")
envInheritance, _ := cmd.Flags().GetBool("env-inheritance")
envPairs, _ := cmd.Flags().GetStringArray("env")
envVars, err := parseEnvPairs(envPairs)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
hostName := getHostname(session, beacon)
// If the user wants to loot or save the output, we have to capture it regardless of if they specified -o
@@ -59,15 +66,19 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
}
var exec *sliverpb.Execute
var err error
ctrl := make(chan bool)
con.PrintInfof("Execute: %s [%s]\n", cmdPath, strings.Join(args, " "))
con.SpinUntil(fmt.Sprintf("Executing %s %s ...", cmdPath, strings.Join(args, " ")), ctrl)
if token || hidden || ppid != 0 {
if (session != nil && session.OS != "windows") || (beacon != nil && beacon.OS != "windows") {
con.PrintErrorf("The token, hide window, and ppid options are not valid on %s\n", session.OS)
return
}
if envInheritance || len(envVars) > 0 {
con.PrintErrorf("The env and env-inheritance options are not supported with token, hidden, or ppid\n")
return
}
exec, err = con.Rpc.ExecuteWindows(context.Background(), &sliverpb.ExecuteWindowsReq{
Request: con.ActiveTarget.Request(cmd),
Path: cmdPath,
@@ -81,12 +92,14 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
})
} else {
exec, err = con.Rpc.Execute(context.Background(), &sliverpb.ExecuteReq{
Request: con.ActiveTarget.Request(cmd),
Path: cmdPath,
Args: args,
Output: captureOutput,
Stderr: stderr,
Stdout: stdout,
Request: con.ActiveTarget.Request(cmd),
Path: cmdPath,
Args: args,
Output: captureOutput,
Stderr: stderr,
Stdout: stdout,
EnvInheritance: envInheritance,
Env: envVars,
})
}
ctrl <- true
@@ -179,6 +192,21 @@ func getHostname(session *clientpb.Session, beacon *clientpb.Beacon) string {
return ""
}
func parseEnvPairs(envPairs []string) (map[string]string, error) {
if len(envPairs) == 0 {
return nil, nil
}
envVars := make(map[string]string, len(envPairs))
for _, pair := range envPairs {
parts := strings.SplitN(pair, "=", 2)
if len(parts) != 2 || parts[0] == "" {
return nil, fmt.Errorf("invalid --env value %q, expected key=value", pair)
}
envVars[parts[0]] = parts[1]
}
return envVars, nil
}
func determineCommandName(command string) string {
commandName := strings.ReplaceAll(command, "\\", "/")
+83 -94
View File
@@ -30,7 +30,6 @@ import (
"os"
"path"
"path/filepath"
"strconv"
"strings"
"github.com/bishopfox/sliver/client/assets"
@@ -43,8 +42,8 @@ import (
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/bishopfox/sliver/util"
"github.com/carapace-sh/carapace"
appConsole "github.com/reeflective/console"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"google.golang.org/protobuf/proto"
@@ -121,6 +120,7 @@ type extensionArgument struct {
Desc string `json:"desc"`
Optional bool `json:"optional"`
Default interface{} `json:"default,omitempty"`
Choices []string `json:"choices,omitempty"`
}
func (e *ExtCommand) getFileForTarget(targetOS string, targetArch string) (string, error) {
@@ -689,94 +689,6 @@ func PrintExtOutput(extName string, commandName string, outputSchema *packages.O
}
}
func getExtArgs(_ *cobra.Command, args []string, _ string, ext *ExtCommand) ([]byte, error) {
var err error
argsBuffer := core.BOFArgsBuffer{
Buffer: new(bytes.Buffer),
}
// Parse BOF arguments from grumble
missingRequiredArgs := make([]string, 0)
// If we have an extension that expects a single string, but more than one has been parsed, combine them
if len(ext.Arguments) == 1 && strings.Contains(ext.Arguments[0].Type, "string") && len(args) > 0 {
// The loop below will only read the first element of args because ext.Arguments is 1
args[0] = strings.Join(args, " ")
}
for _, arg := range ext.Arguments {
// If we don't have any positional words left to consume,
// add the remaining required extension arguments in the
// error message.
if len(args) == 0 {
if !arg.Optional {
missingRequiredArgs = append(missingRequiredArgs, "`"+arg.Name+"`")
}
continue
}
// Else pop a word from the list
word := args[0]
args = args[1:]
switch arg.Type {
case "integer":
fallthrough
case "int":
val, err := strconv.Atoi(word)
if err != nil {
return nil, err
}
err = argsBuffer.AddInt(uint32(val))
if err != nil {
return nil, err
}
case "short":
val, err := strconv.Atoi(word)
if err != nil {
return nil, err
}
err = argsBuffer.AddShort(uint16(val))
if err != nil {
return nil, err
}
case "string":
err = argsBuffer.AddString(word)
if err != nil {
return nil, err
}
case "wstring":
err = argsBuffer.AddWString(word)
if err != nil {
return nil, err
}
// Adding support for filepaths so we can
// send binary data like shellcodes to BOFs
case "file":
data, err := os.ReadFile(word)
if err != nil {
return nil, err
}
err = argsBuffer.AddData(data)
if err != nil {
return nil, err
}
}
}
// Return if we have missing required arguments
if len(missingRequiredArgs) > 0 {
return nil, fmt.Errorf("required arguments %s were not provided", strings.Join(missingRequiredArgs, ", "))
}
parsedArgs, err := argsBuffer.GetBuffer()
if err != nil {
return nil, err
}
return parsedArgs, nil
}
func getBOFArgs(cmd *cobra.Command, args []string, binPath string, ext *ExtCommand) ([]byte, error) {
var extensionArgs []byte
binData, err := os.ReadFile(binPath)
@@ -837,16 +749,27 @@ func checkExtensionArgs(extCmd *ExtCommand) error {
return nil
}
// makeExtensionArgCompleter builds the positional arguments completer for the extension.
// makeExtensionArgCompleter builds the positional and dash arguments completer for the extension.
// It provides completion for:
// 1. Positional arguments (before --)
// 2. Flag-style arguments after -- (e.g., --process, --shellcode)
func makeExtensionArgCompleter(extCmd *ExtCommand, _ *cobra.Command, comps *carapace.Carapace) {
var actions []carapace.Action
for _, arg := range extCmd.Arguments {
var action carapace.Action
switch arg.Type {
case "file":
action = carapace.ActionFiles().Tag("extension data")
// If choices are defined, use them for completion
if len(arg.Choices) > 0 {
action = carapace.ActionValues(arg.Choices...).Tag("choices")
} else {
// Fall back to type-based completion
switch arg.Type {
case "file":
action = carapace.ActionFiles().Tag("extension data")
default:
action = carapace.ActionValues()
}
}
usage := fmt.Sprintf("(%s) %s", arg.Type, arg.Desc)
@@ -858,6 +781,72 @@ func makeExtensionArgCompleter(extCmd *ExtCommand, _ *cobra.Command, comps *cara
}
comps.PositionalCompletion(actions...)
// Add dash completion for flag-style arguments after --
// The extension arguments are parsed as flags (--name value) by argparser.go
if len(extCmd.Arguments) > 0 {
// Pre-build the flag completions at registration time (not in a callback)
// This ensures the values are captured correctly
flagCompletion := makeExtensionFlagNameCompletion(extCmd)
// Build value completions for each argument type
valueCompletions := make(map[string]carapace.Action)
for _, arg := range extCmd.Arguments {
// If choices are defined, use them for completion
if len(arg.Choices) > 0 {
valueCompletions[arg.Name] = carapace.ActionValues(arg.Choices...).Tag("choices")
} else {
// Fall back to type-based completion
switch arg.Type {
case "file":
valueCompletions[arg.Name] = carapace.ActionFiles().Tag("file path")
default:
valueCompletions[arg.Name] = carapace.ActionValues()
}
}
}
// Use DashAnyCompletion with a smart action that determines context
comps.DashAnyCompletion(
carapace.ActionCallback(func(c carapace.Context) carapace.Action {
// If typing a flag (starts with -)
if strings.HasPrefix(c.Value, "-") {
return flagCompletion
}
// If previous arg was a flag, complete its value
if len(c.Args) > 0 {
lastArg := c.Args[len(c.Args)-1]
if strings.HasPrefix(lastArg, "-") {
flagName := strings.TrimLeft(lastArg, "-")
if action, ok := valueCompletions[flagName]; ok {
return action
}
}
}
// Default: show flag names
return flagCompletion
}),
)
}
}
// makeExtensionFlagNameCompletion creates completion for flag names
func makeExtensionFlagNameCompletion(extCmd *ExtCommand) carapace.Action {
var results []string
for _, arg := range extCmd.Arguments {
flagName := fmt.Sprintf("--%s", arg.Name)
desc := arg.Desc
if arg.Optional {
desc = fmt.Sprintf("[optional] %s", desc)
}
desc = fmt.Sprintf("(%s) %s", arg.Type, desc)
results = append(results, flagName, desc)
}
return carapace.ActionValuesDescribed(results...).Tag("extension arguments")
}
func makeCommandPlatformFilters(extCmd *ExtCommand) map[string]string {
+17
View File
@@ -68,6 +68,7 @@ var (
consts.RunAsStr: runAsHelp,
consts.ImpersonateStr: impersonateHelp,
consts.RevToSelfStr: revToSelfHelp,
consts.ExecuteStr: executeHelp,
consts.ExecuteAssemblyStr: executeAssemblyHelp,
consts.ExecuteShellcodeStr: executeShellcodeHelp,
consts.MigrateStr: migrateHelp,
@@ -124,6 +125,22 @@ var (
consts.C2ProfileStr + sep + consts.C2GenerateStr: c2GenerateHelp,
}
executeHelp = `[[.Bold]]Command:[[.Normal]] execute <path to executable> [options] -- [args...]
[[.Bold]]About:[[.Normal]] Execute a program on the remote system with optional arguments, note the subprocess
arguments are be separated from the sliver 'execute' arguments with '--'
[[.Bold]]Example:[[.Normal]]
execute /bin/bash --env FOO=1 -- -c env
[*] Execute: /bin/bash [-c env]
[*] Output:
FOO=1
PWD=/Users/moloch/git/sliver
SHLVL=1
_=/usr/bin/env
`
jobsHelp = `[[.Bold]]Command:[[.Normal]] jobs <options>
[[.Bold]]About:[[.Normal]] Manage jobs/listeners.`
+12
View File
@@ -26,6 +26,7 @@ import (
"github.com/spf13/cobra"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
)
@@ -44,6 +45,17 @@ func GetSystemCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
process, _ := cmd.Flags().GetString("process")
config := con.GetActiveSessionConfig()
/* If the HTTP C2 Config name is not defined, then put in the default value
This will have no effect on implants that do not use HTTP C2
Also this should be overridden when the build info is pulled from the
database, but if there is no build info and we have to create the build
from scratch, we need to have something in here.
*/
if config.HTTPC2ConfigName == "" {
config.HTTPC2ConfigName = consts.DefaultC2Profile
}
ctrl := make(chan bool)
con.SpinUntil("Attempting to create a new sliver session as 'NT AUTHORITY\\SYSTEM'...", ctrl)
+10 -8
View File
@@ -1,6 +1,6 @@
module github.com/bishopfox/sliver
go 1.25.3
go 1.25.5
replace github.com/rsteube/carapace v0.36.3 => github.com/reeflective/carapace v0.46.3-0.20231214181515-27e49f3c3b69
@@ -10,7 +10,7 @@ require (
filippo.io/age v1.3.1
github.com/Binject/binjection v0.0.0-20210701074423-605d46e35deb
github.com/Binject/debug v0.0.0-20230508195519-26db73212a7a
github.com/Binject/go-donut v0.0.0-20220908180326-fcdcc35d591c
github.com/Binject/go-donut v0.0.0-20210701074227-67a31e2d883e
github.com/Binject/universal v0.0.0-20220519011857-bea739e758c0
github.com/Ne0nd0g/go-clr v1.0.3
github.com/alecthomas/chroma v0.10.0
@@ -33,7 +33,7 @@ require (
github.com/lesnuages/go-winio v0.4.19
github.com/lesnuages/snitch v0.6.0
github.com/mattn/go-sqlite3 v1.14.32
github.com/miekg/dns v1.1.69
github.com/miekg/dns v1.1.70
github.com/moloch--/asciicast v0.1.1
github.com/moloch--/memmod v0.0.0-20230225130813-fd77d905589e
github.com/moloch--/sgn v0.0.4
@@ -53,29 +53,31 @@ require (
golang.org/x/crypto v0.46.0
golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9
golang.org/x/mod v0.31.0
golang.org/x/net v0.47.0
golang.org/x/sys v0.39.0
golang.org/x/net v0.48.0
golang.org/x/sys v0.40.0
golang.org/x/term v0.38.0
golang.org/x/text v0.32.0
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10
google.golang.org/grpc v1.76.0
google.golang.org/protobuf v1.36.10
google.golang.org/protobuf v1.36.11
gorm.io/driver/mysql v1.6.0
gorm.io/driver/postgres v1.6.0
gorm.io/driver/sqlite v1.6.0
gorm.io/gorm v1.31.0
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c
modernc.org/sqlite v1.39.0
tailscale.com v1.90.6
tailscale.com v1.92.5
)
require (
filippo.io/hpke v0.4.0 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/creachadair/msync v0.7.1 // indirect
github.com/invopop/jsonschema v0.13.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/pires/go-proxyproto v0.8.1 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
@@ -183,7 +185,7 @@ require (
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/time v0.13.0 // indirect
golang.org/x/tools v0.39.0 // indirect
golang.org/x/tools v0.40.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f // indirect
+24 -20
View File
@@ -19,8 +19,8 @@ github.com/Binject/debug v0.0.0-20210225042342-c9b8b45728d2/go.mod h1:QzgxDLY/qd
github.com/Binject/debug v0.0.0-20210312092933-6277045c2fdf/go.mod h1:QzgxDLY/qdKlvnbnb65eqTedhvQPbaSP2NqIbcuKvsQ=
github.com/Binject/debug v0.0.0-20230508195519-26db73212a7a h1:4c0nc0krv8eh7gD809n+swLaCuFyHpxdrxwx0ZmHvBw=
github.com/Binject/debug v0.0.0-20230508195519-26db73212a7a/go.mod h1:QzgxDLY/qdKlvnbnb65eqTedhvQPbaSP2NqIbcuKvsQ=
github.com/Binject/go-donut v0.0.0-20220908180326-fcdcc35d591c h1:cFMQKxryHZ3Sddca8nqJ1RDjaUTW/+79lRKs+7508nk=
github.com/Binject/go-donut v0.0.0-20220908180326-fcdcc35d591c/go.mod h1:dc3mUnr4KTKcFKVq7BVbHGF0xAHrIyooQ+VTO7/bIZw=
github.com/Binject/go-donut v0.0.0-20210701074227-67a31e2d883e h1:ytVmxGQuS7ELO/WpvH6iuY1hVcJ6iOTw3VLOOIFlo8o=
github.com/Binject/go-donut v0.0.0-20210701074227-67a31e2d883e/go.mod h1:dc3mUnr4KTKcFKVq7BVbHGF0xAHrIyooQ+VTO7/bIZw=
github.com/Binject/shellcode v0.0.0-20191101084904-a8a90e7d4563 h1:T8z8Wz/fqaPPANF8Unv4LjWLoa0TAvsjYAsRpvSkOqs=
github.com/Binject/shellcode v0.0.0-20191101084904-a8a90e7d4563/go.mod h1:HMbzsKPz1sF7H4Hmeovh+d2PH2iKPYpAB4XMOVz8wmM=
github.com/Binject/universal v0.0.0-20220519011857-bea739e758c0 h1:1sdnMvqxxlMXsu8ku/Xrh6gi3V40ykksst5fd0lfWnc=
@@ -148,6 +148,8 @@ github.com/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNs
github.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creachadair/msync v0.7.1 h1:SeZmuEBXQPe5GqV/C94ER7QIZPwtvFbeQiykzt/7uho=
github.com/creachadair/msync v0.7.1/go.mod h1:8CcFlLsSujfHE5wWm19uUBLHIPDAUr6LXDwneVMO008=
github.com/creachadair/taskgroup v0.13.2 h1:3KyqakBuFsm3KkXi/9XIb0QcA8tEzLHLgaoidf0MdVc=
github.com/creachadair/taskgroup v0.13.2/go.mod h1:i3V1Zx7H8RjwljUEeUWYT30Lmb9poewSb2XI1yTwD0g=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
@@ -340,8 +342,8 @@ github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ
github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE=
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
github.com/miekg/dns v1.1.69 h1:Kb7Y/1Jo+SG+a2GtfoFUfDkG//csdRPwRLkCsxDG9Sc=
github.com/miekg/dns v1.1.69/go.mod h1:7OyjD9nEba5OkqQ/hB4fy3PIoxafSZJtducccIelz3g=
github.com/miekg/dns v1.1.70 h1:DZ4u2AV35VJxdD9Fo9fIWm119BsQL5cZU1cQ9s0LkqA=
github.com/miekg/dns v1.1.70/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
@@ -379,6 +381,8 @@ github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhA
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
@@ -389,10 +393,10 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH
github.com/prometheus-community/pro-bing v0.4.0 h1:YMbv+i08gQz97OZZBwLyvmmQEEzyfyrrjEaAchdy3R4=
github.com/prometheus-community/pro-bing v0.4.0/go.mod h1:b7wRYZtCcPmt4Sz319BykUU241rWLe1VFXyiyWK/dH4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
github.com/reeflective/console v0.1.25 h1:do/r6rcKH6pCQDEzh5PhoVFcqHkqcjd0N6tNjpmYWWQ=
github.com/reeflective/console v0.1.25/go.mod h1:It/IqEjo0PowaBuxBS9pEi5EYUgmtvQd0ytMtlS77kk=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
@@ -534,8 +538,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
@@ -564,8 +568,8 @@ golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -584,8 +588,8 @@ golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -614,8 +618,8 @@ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
@@ -648,8 +652,8 @@ gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c h1:m/r7OM+Y2Ty1sgBQ7Qb27VgI
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c/go.mod h1:3r5CMtNQMKIvBlrmM9xWUNamjKBYPOWyXOjmg5Kts3g=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.5.1 h1:4bH5o3b5ZULQ4UrBmP+63W9r7qIkqJClEA9ko5YKx+I=
honnef.co/go/tools v0.5.1/go.mod h1:e9irvo83WDG9/irijV44wr3tbhcFeRnfpVlRqVwpzMs=
honnef.co/go/tools v0.7.0-0.dev.0.20251022135355-8273271481d0 h1:5SXjd4ET5dYijLaf0O3aOenC0Z4ZafIWSpjUzsQaNho=
honnef.co/go/tools v0.7.0-0.dev.0.20251022135355-8273271481d0/go.mod h1:EPDDhEZqVHhWuPI5zPAsjU0U7v9xNIWjoOVyZ5ZcniQ=
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
modernc.org/cc/v4 v4.26.5 h1:xM3bX7Mve6G8K8b+T11ReenJOT+BmVqQj0FY5T4+5Y4=
@@ -682,5 +686,5 @@ mvdan.cc/sh/v3 v3.12.0 h1:ejKUR7ONP5bb+UGHGEG/k9V5+pRVIyD+LsZz7o8KHrI=
mvdan.cc/sh/v3 v3.12.0/go.mod h1:Se6Cj17eYSn+sNooLZiEUnNNmNxg0imoYlTu4CyaGyg=
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
tailscale.com v1.90.6 h1:EhYPiZP/xcLeinikaLA0kn4CQT3+z9SZ13IB/kzWhd4=
tailscale.com v1.90.6/go.mod h1:+9EX6pOGCNa6pxCVRhhlJLy/qnkDzOplFYpeZyYlCT0=
tailscale.com v1.92.5 h1:h88HqtGk8jd9umF7l6m/8+QK69dY9IVMIlkNK9XYpvQ=
tailscale.com v1.92.5/go.mod h1:jzTfKDd6XNNSNoX+Q9INIMpMU5rfZ9g8ojcAoMKi5w0=
+25
View File
@@ -27,10 +27,12 @@ import (
"fmt"
"io"
"io/fs"
"maps"
"os"
"os/exec"
"path/filepath"
"regexp"
"sort"
"strings"
"time"
"unicode/utf8"
@@ -1146,6 +1148,29 @@ func executeHandler(data []byte, resp RPCResponse) {
return
}
cmd := exec.Command(exePath, execReq.Args...)
if execReq.EnvInheritance || len(execReq.Env) > 0 {
envVars := make(map[string]string)
if execReq.EnvInheritance {
for _, entry := range os.Environ() {
parts := strings.SplitN(entry, "=", 2)
if len(parts) == 2 {
envVars[parts[0]] = parts[1]
} else if len(parts) == 1 {
envVars[parts[0]] = ""
}
}
}
maps.Copy(envVars, execReq.Env)
keys := make([]string, 0, len(envVars))
for key := range envVars {
keys = append(keys, key)
}
sort.Strings(keys)
cmd.Env = make([]string, 0, len(envVars))
for _, key := range keys {
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", key, envVars[key]))
}
}
if execReq.Output {
stdOutBuff := new(bytes.Buffer)
File diff suppressed because it is too large. Load diff
+3
View File
@@ -515,6 +515,9 @@ message ExecuteReq {
bool Output = 3;
string Stdout = 4;
string Stderr = 5;
bool EnvInheritance = 6;
map<string, string> Env = 7;
uint32 PPid = 10;
commonpb.Request Request = 9;
+6
View File
@@ -109,6 +109,12 @@ func (rpc *Server) GetSystem(ctx context.Context, req *clientpb.GetSystemReq) (*
if err != nil {
req.Config.Format = clientpb.OutputFormat_SHELLCODE
req.Config.ObfuscateSymbols = false
req.Config.IsShellcode = true
req.Config.IsSharedLib = false
req.Config.TemplateName = "sliver"
if len(req.Config.Exports) == 0 {
req.Config.Exports = []string{"StartW"}
}
build, err := generate.GenerateConfig(name, req.Config)
if err != nil {
return nil, rpcError(err)
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+26
View File
@@ -0,0 +1,26 @@
Copyright (C) 2022, Michael J. Fromberger
All Rights Reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
(1) Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
(2) Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
(3) The name of the author may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
+81
View File
@@ -0,0 +1,81 @@
// Package trigger implements channel-based condition variable.
package trigger
import "sync"
// A Cond is a condition shared by multiple goroutines. The [Cond.Ready]
// method returns a channel that is closed when the condition is activated.
//
// When a condition is first created it is inactive. While inactive, reads on
// the ready channel will block. The condition remains inactive until
// [Cond.Set] or [Cond.Signal] is called, either of which causes the current
// ready channel to be closed (and thus deliver a zero value). Once a condition
// has been activated, it remains active until it is reset. Use [Cond.Reset] to
// make it inactive again.
//
// The [Cond.Signal] method activates and then immediately resets the
// condition, acting as Set and Reset done in a single step.
//
// A zero Cond is ready for use, and is inactive, but must not be copied after
// any of its methods have been called.
type Cond struct {
μ sync.Mutex
ch chan struct{}
closed bool
// The signal channel is lazily initialized by the first waiter.
}
// New constructs a new inactive [Cond].
func New() *Cond { return new(Cond) }
// Signal activates and immediately resets the condition. If the condition was
// already active, this is equivalent to [Cond.Reset].
func (c *Cond) Signal() {
c.μ.Lock()
defer c.μ.Unlock()
if c.ch != nil && !c.closed {
close(c.ch) // wake any pending waiters
}
c.ch = nil
c.closed = false
}
// Set activates the condition. If it was already active, Set has no effect.
func (c *Cond) Set() {
c.μ.Lock()
defer c.μ.Unlock()
if c.ch == nil {
c.ch = make(chan struct{})
}
if !c.closed {
close(c.ch)
c.closed = true
}
}
// Reset resets the condition. If it was already inactive, Reset has no effect.
func (c *Cond) Reset() {
c.μ.Lock()
defer c.μ.Unlock()
if c.closed {
c.ch = nil
c.closed = false
}
}
// Ready returns a channel that is closed when c is activated. If c is active
// when Ready is called, the returned channel will already be closed.
func (c *Cond) Ready() <-chan struct{} {
c.μ.Lock()
defer c.μ.Unlock()
if c.ch == nil {
c.ch = make(chan struct{})
c.closed = false
}
return c.ch
}
+7 -2
View File
@@ -6,7 +6,12 @@
DNS version 2 is now available at <https://codeberg.org/miekg/dns>, check it out if you want to
help shape the next 15 years of the Go DNS package.
The version here will see no new features and less and less development.
The version here will see no new features and less and less development, and my time (if any) will be fully
devoted towards v2.
**December 2025**: v2 should be (already) a good replacement, the coming months would be a good time to
migrate, see [this file describing the
differences](https://codeberg.org/miekg/dns/src/branch/main/README-diff-with-v1.md), to help you get started.
# Alternative (more granular) approach to a DNS library
@@ -62,7 +67,7 @@ A not-so-up-to-date-list-that-may-be-actually-current:
- https://www.dnsperf.com/
- https://dnssectest.net/
- https://github.com/oif/apex
- https://github.com/jedisct1/dnscrypt-proxy
- https://github.com/jedisct1/dnscrypt-proxy (migrated to v2)
- https://github.com/jedisct1/rpdns
- https://github.com/xor-gate/sshfp
- https://github.com/rs/dnstrace
+7 -3
View File
@@ -5,6 +5,7 @@ import (
"fmt"
"io"
"io/fs"
"math"
"os"
"path"
"path/filepath"
@@ -1231,7 +1232,7 @@ func typeToInt(token string) (uint16, bool) {
// stringToTTL parses things like 2w, 2m, etc, and returns the time in seconds.
func stringToTTL(token string) (uint32, bool) {
var s, i uint32
var s, i uint
for _, c := range token {
switch c {
case 's', 'S':
@@ -1251,12 +1252,15 @@ func stringToTTL(token string) (uint32, bool) {
i = 0
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
i *= 10
i += uint32(c) - '0'
i += uint(c) - '0'
default:
return 0, false
}
}
return s + i, true
if s+i > math.MaxUint32 {
return 0, false
}
return uint32(s + i), true
}
// Parse LOC records' <digits>[.<digits>][mM] into a
+1 -1
View File
@@ -3,7 +3,7 @@ package dns
import "fmt"
// Version is current version of this library.
var Version = v{1, 1, 69}
var Version = v{1, 1, 70}
// v holds the version of this library.
type v struct {
+11
View File
@@ -0,0 +1,11 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
.idea
bin
pkg
*.out
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2016 Paulo Pires
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+162
View File
@@ -0,0 +1,162 @@
# go-proxyproto
[![Actions Status](https://github.com/pires/go-proxyproto/workflows/test/badge.svg)](https://github.com/pires/go-proxyproto/actions)
[![Coverage Status](https://coveralls.io/repos/github/pires/go-proxyproto/badge.svg?branch=master)](https://coveralls.io/github/pires/go-proxyproto?branch=master)
[![Go Report Card](https://goreportcard.com/badge/github.com/pires/go-proxyproto)](https://goreportcard.com/report/github.com/pires/go-proxyproto)
[![](https://godoc.org/github.com/pires/go-proxyproto?status.svg)](https://pkg.go.dev/github.com/pires/go-proxyproto?tab=doc)
A Go library implementation of the [PROXY protocol, versions 1 and 2](https://www.haproxy.org/download/2.3/doc/proxy-protocol.txt),
which provides, as per specification:
> (...) a convenient way to safely transport connection
> information such as a client's address across multiple layers of NAT or TCP
> proxies. It is designed to require little changes to existing components and
> to limit the performance impact caused by the processing of the transported
> information.
This library is to be used in one of or both proxy clients and proxy servers that need to support said protocol.
Both protocol versions, 1 (text-based) and 2 (binary-based) are supported.
## Installation
```shell
$ go get -u github.com/pires/go-proxyproto
```
## Usage
### Client
```go
package main
import (
"io"
"log"
"net"
proxyproto "github.com/pires/go-proxyproto"
)
func chkErr(err error) {
if err != nil {
log.Fatalf("Error: %s", err.Error())
}
}
func main() {
// Dial some proxy listener e.g. https://github.com/mailgun/proxyproto
target, err := net.ResolveTCPAddr("tcp", "127.0.0.1:2319")
chkErr(err)
conn, err := net.DialTCP("tcp", nil, target)
chkErr(err)
defer conn.Close()
// Create a proxyprotocol header or use HeaderProxyFromAddrs() if you
// have two conn's
header := &proxyproto.Header{
Version: 1,
Command: proxyproto.PROXY,
TransportProtocol: proxyproto.TCPv4,
SourceAddr: &net.TCPAddr{
IP: net.ParseIP("10.1.1.1"),
Port: 1000,
},
DestinationAddr: &net.TCPAddr{
IP: net.ParseIP("20.2.2.2"),
Port: 2000,
},
}
// After the connection was created write the proxy headers first
_, err = header.WriteTo(conn)
chkErr(err)
// Then your data... e.g.:
_, err = io.WriteString(conn, "HELO")
chkErr(err)
}
```
### Server
```go
package main
import (
"log"
"net"
proxyproto "github.com/pires/go-proxyproto"
)
func main() {
// Create a listener
addr := "localhost:9876"
list, err := net.Listen("tcp", addr)
if err != nil {
log.Fatalf("couldn't listen to %q: %q\n", addr, err.Error())
}
// Wrap listener in a proxyproto listener
proxyListener := &proxyproto.Listener{Listener: list}
defer proxyListener.Close()
// Wait for a connection and accept it
conn, err := proxyListener.Accept()
defer conn.Close()
// Print connection details
if conn.LocalAddr() == nil {
log.Fatal("couldn't retrieve local address")
}
log.Printf("local address: %q", conn.LocalAddr().String())
if conn.RemoteAddr() == nil {
log.Fatal("couldn't retrieve remote address")
}
log.Printf("remote address: %q", conn.RemoteAddr().String())
}
```
### HTTP Server
```go
package main
import (
"net"
"net/http"
"time"
"github.com/pires/go-proxyproto"
)
func main() {
server := http.Server{
Addr: ":8080",
}
ln, err := net.Listen("tcp", server.Addr)
if err != nil {
panic(err)
}
proxyListener := &proxyproto.Listener{
Listener: ln,
ReadHeaderTimeout: 10 * time.Second,
}
defer proxyListener.Close()
server.Serve(proxyListener)
}
```
## Special notes
### AWS
AWS Network Load Balancer (NLB) does not push the PPV2 header until the client starts sending the data. This is a problem if your server speaks first. e.g. SMTP, FTP, SSH etc.
By default, NLB target group attribute `proxy_protocol_v2.client_to_server.header_placement` has the value `on_first_ack_with_payload`. You need to contact AWS support to change it to `on_first_ack`, instead.
Just to be clear, you need this fix only if your server is designed to speak first.
+62
View File
@@ -0,0 +1,62 @@
package proxyproto
// AddressFamilyAndProtocol represents address family and transport protocol.
type AddressFamilyAndProtocol byte
const (
UNSPEC AddressFamilyAndProtocol = '\x00'
TCPv4 AddressFamilyAndProtocol = '\x11'
UDPv4 AddressFamilyAndProtocol = '\x12'
TCPv6 AddressFamilyAndProtocol = '\x21'
UDPv6 AddressFamilyAndProtocol = '\x22'
UnixStream AddressFamilyAndProtocol = '\x31'
UnixDatagram AddressFamilyAndProtocol = '\x32'
)
// IsIPv4 returns true if the address family is IPv4 (AF_INET4), false otherwise.
func (ap AddressFamilyAndProtocol) IsIPv4() bool {
return ap&0xF0 == 0x10
}
// IsIPv6 returns true if the address family is IPv6 (AF_INET6), false otherwise.
func (ap AddressFamilyAndProtocol) IsIPv6() bool {
return ap&0xF0 == 0x20
}
// IsUnix returns true if the address family is UNIX (AF_UNIX), false otherwise.
func (ap AddressFamilyAndProtocol) IsUnix() bool {
return ap&0xF0 == 0x30
}
// IsStream returns true if the transport protocol is TCP or STREAM (SOCK_STREAM), false otherwise.
func (ap AddressFamilyAndProtocol) IsStream() bool {
return ap&0x0F == 0x01
}
// IsDatagram returns true if the transport protocol is UDP or DGRAM (SOCK_DGRAM), false otherwise.
func (ap AddressFamilyAndProtocol) IsDatagram() bool {
return ap&0x0F == 0x02
}
// IsUnspec returns true if the transport protocol or address family is unspecified, false otherwise.
func (ap AddressFamilyAndProtocol) IsUnspec() bool {
return (ap&0xF0 == 0x00) || (ap&0x0F == 0x00)
}
func (ap AddressFamilyAndProtocol) toByte() byte {
if ap.IsIPv4() && ap.IsStream() {
return byte(TCPv4)
} else if ap.IsIPv4() && ap.IsDatagram() {
return byte(UDPv4)
} else if ap.IsIPv6() && ap.IsStream() {
return byte(TCPv6)
} else if ap.IsIPv6() && ap.IsDatagram() {
return byte(UDPv6)
} else if ap.IsUnix() && ap.IsStream() {
return byte(UnixStream)
} else if ap.IsUnix() && ap.IsDatagram() {
return byte(UnixDatagram)
}
return byte(UNSPEC)
}
+280
View File
@@ -0,0 +1,280 @@
// Package proxyproto implements Proxy Protocol (v1 and v2) parser and writer, as per specification:
// https://www.haproxy.org/download/2.3/doc/proxy-protocol.txt
package proxyproto
import (
"bufio"
"bytes"
"errors"
"io"
"net"
"time"
)
var (
// Protocol
SIGV1 = []byte{'\x50', '\x52', '\x4F', '\x58', '\x59'}
SIGV2 = []byte{'\x0D', '\x0A', '\x0D', '\x0A', '\x00', '\x0D', '\x0A', '\x51', '\x55', '\x49', '\x54', '\x0A'}
ErrCantReadVersion1Header = errors.New("proxyproto: can't read version 1 header")
ErrVersion1HeaderTooLong = errors.New("proxyproto: version 1 header must be 107 bytes or less")
ErrLineMustEndWithCrlf = errors.New("proxyproto: version 1 header is invalid, must end with \\r\\n")
ErrCantReadProtocolVersionAndCommand = errors.New("proxyproto: can't read proxy protocol version and command")
ErrCantReadAddressFamilyAndProtocol = errors.New("proxyproto: can't read address family or protocol")
ErrCantReadLength = errors.New("proxyproto: can't read length")
ErrCantResolveSourceUnixAddress = errors.New("proxyproto: can't resolve source Unix address")
ErrCantResolveDestinationUnixAddress = errors.New("proxyproto: can't resolve destination Unix address")
ErrNoProxyProtocol = errors.New("proxyproto: proxy protocol signature not present")
ErrUnknownProxyProtocolVersion = errors.New("proxyproto: unknown proxy protocol version")
ErrUnsupportedProtocolVersionAndCommand = errors.New("proxyproto: unsupported proxy protocol version and command")
ErrUnsupportedAddressFamilyAndProtocol = errors.New("proxyproto: unsupported address family and protocol")
ErrInvalidLength = errors.New("proxyproto: invalid length")
ErrInvalidAddress = errors.New("proxyproto: invalid address")
ErrInvalidPortNumber = errors.New("proxyproto: invalid port number")
ErrSuperfluousProxyHeader = errors.New("proxyproto: upstream connection sent PROXY header but isn't allowed to send one")
)
// Header is the placeholder for proxy protocol header.
type Header struct {
Version byte
Command ProtocolVersionAndCommand
TransportProtocol AddressFamilyAndProtocol
SourceAddr net.Addr
DestinationAddr net.Addr
rawTLVs []byte
}
// HeaderProxyFromAddrs creates a new PROXY header from a source and a
// destination address. If version is zero, the latest protocol version is
// used.
//
// The header is filled on a best-effort basis: if hints cannot be inferred
// from the provided addresses, the header will be left unspecified.
func HeaderProxyFromAddrs(version byte, sourceAddr, destAddr net.Addr) *Header {
if version < 1 || version > 2 {
version = 2
}
h := &Header{
Version: version,
Command: LOCAL,
TransportProtocol: UNSPEC,
}
switch sourceAddr := sourceAddr.(type) {
case *net.TCPAddr:
if _, ok := destAddr.(*net.TCPAddr); !ok {
break
}
if len(sourceAddr.IP.To4()) == net.IPv4len {
h.TransportProtocol = TCPv4
} else if len(sourceAddr.IP) == net.IPv6len {
h.TransportProtocol = TCPv6
}
case *net.UDPAddr:
if _, ok := destAddr.(*net.UDPAddr); !ok {
break
}
if len(sourceAddr.IP.To4()) == net.IPv4len {
h.TransportProtocol = UDPv4
} else if len(sourceAddr.IP) == net.IPv6len {
h.TransportProtocol = UDPv6
}
case *net.UnixAddr:
if _, ok := destAddr.(*net.UnixAddr); !ok {
break
}
switch sourceAddr.Net {
case "unix":
h.TransportProtocol = UnixStream
case "unixgram":
h.TransportProtocol = UnixDatagram
}
}
if h.TransportProtocol != UNSPEC {
h.Command = PROXY
h.SourceAddr = sourceAddr
h.DestinationAddr = destAddr
}
return h
}
func (header *Header) TCPAddrs() (sourceAddr, destAddr *net.TCPAddr, ok bool) {
if !header.TransportProtocol.IsStream() {
return nil, nil, false
}
sourceAddr, sourceOK := header.SourceAddr.(*net.TCPAddr)
destAddr, destOK := header.DestinationAddr.(*net.TCPAddr)
return sourceAddr, destAddr, sourceOK && destOK
}
func (header *Header) UDPAddrs() (sourceAddr, destAddr *net.UDPAddr, ok bool) {
if !header.TransportProtocol.IsDatagram() {
return nil, nil, false
}
sourceAddr, sourceOK := header.SourceAddr.(*net.UDPAddr)
destAddr, destOK := header.DestinationAddr.(*net.UDPAddr)
return sourceAddr, destAddr, sourceOK && destOK
}
func (header *Header) UnixAddrs() (sourceAddr, destAddr *net.UnixAddr, ok bool) {
if !header.TransportProtocol.IsUnix() {
return nil, nil, false
}
sourceAddr, sourceOK := header.SourceAddr.(*net.UnixAddr)
destAddr, destOK := header.DestinationAddr.(*net.UnixAddr)
return sourceAddr, destAddr, sourceOK && destOK
}
func (header *Header) IPs() (sourceIP, destIP net.IP, ok bool) {
if sourceAddr, destAddr, ok := header.TCPAddrs(); ok {
return sourceAddr.IP, destAddr.IP, true
} else if sourceAddr, destAddr, ok := header.UDPAddrs(); ok {
return sourceAddr.IP, destAddr.IP, true
} else {
return nil, nil, false
}
}
func (header *Header) Ports() (sourcePort, destPort int, ok bool) {
if sourceAddr, destAddr, ok := header.TCPAddrs(); ok {
return sourceAddr.Port, destAddr.Port, true
} else if sourceAddr, destAddr, ok := header.UDPAddrs(); ok {
return sourceAddr.Port, destAddr.Port, true
} else {
return 0, 0, false
}
}
// EqualTo returns true if headers are equivalent, false otherwise.
// Deprecated: use EqualsTo instead. This method will eventually be removed.
func (header *Header) EqualTo(otherHeader *Header) bool {
return header.EqualsTo(otherHeader)
}
// EqualsTo returns true if headers are equivalent, false otherwise.
func (header *Header) EqualsTo(otherHeader *Header) bool {
if otherHeader == nil {
return false
}
if header.Version != otherHeader.Version || header.Command != otherHeader.Command || header.TransportProtocol != otherHeader.TransportProtocol {
return false
}
// TLVs only exist for version 2
if header.Version == 2 && !bytes.Equal(header.rawTLVs, otherHeader.rawTLVs) {
return false
}
// Return early for header with LOCAL command, which contains no address information
if header.Command == LOCAL {
return true
}
return header.SourceAddr.String() == otherHeader.SourceAddr.String() &&
header.DestinationAddr.String() == otherHeader.DestinationAddr.String()
}
// WriteTo renders a proxy protocol header in a format and writes it to an io.Writer.
func (header *Header) WriteTo(w io.Writer) (int64, error) {
buf, err := header.Format()
if err != nil {
return 0, err
}
return bytes.NewBuffer(buf).WriteTo(w)
}
// Format renders a proxy protocol header in a format to write over the wire.
func (header *Header) Format() ([]byte, error) {
switch header.Version {
case 1:
return header.formatVersion1()
case 2:
return header.formatVersion2()
default:
return nil, ErrUnknownProxyProtocolVersion
}
}
// TLVs returns the TLVs stored into this header, if they exist. TLVs are optional for v2 of the protocol.
func (header *Header) TLVs() ([]TLV, error) {
return SplitTLVs(header.rawTLVs)
}
// SetTLVs sets the TLVs stored in this header. This method replaces any
// previous TLV.
func (header *Header) SetTLVs(tlvs []TLV) error {
raw, err := JoinTLVs(tlvs)
if err != nil {
return err
}
header.rawTLVs = raw
return nil
}
// Read identifies the proxy protocol version and reads the remaining of
// the header, accordingly.
//
// If proxy protocol header signature is not present, the reader buffer remains untouched
// and is safe for reading outside of this code.
//
// If proxy protocol header signature is present but an error is raised while processing
// the remaining header, assume the reader buffer to be in a corrupt state.
// Also, this operation will block until enough bytes are available for peeking.
func Read(reader *bufio.Reader) (*Header, error) {
// In order to improve speed for small non-PROXYed packets, take a peek at the first byte alone.
b1, err := reader.Peek(1)
if err != nil {
if err == io.EOF {
return nil, ErrNoProxyProtocol
}
return nil, err
}
if bytes.Equal(b1[:1], SIGV1[:1]) || bytes.Equal(b1[:1], SIGV2[:1]) {
signature, err := reader.Peek(5)
if err != nil {
if err == io.EOF {
return nil, ErrNoProxyProtocol
}
return nil, err
}
if bytes.Equal(signature[:5], SIGV1) {
return parseVersion1(reader)
}
signature, err = reader.Peek(12)
if err != nil {
if err == io.EOF {
return nil, ErrNoProxyProtocol
}
return nil, err
}
if bytes.Equal(signature[:12], SIGV2) {
return parseVersion2(reader)
}
}
return nil, ErrNoProxyProtocol
}
// ReadTimeout acts as Read but takes a timeout. If that timeout is reached, it's assumed
// there's no proxy protocol header.
func ReadTimeout(reader *bufio.Reader, timeout time.Duration) (*Header, error) {
type header struct {
h *Header
e error
}
read := make(chan *header, 1)
go func() {
h := &header{}
h.h, h.e = Read(reader)
read <- h
}()
timer := time.NewTimer(timeout)
select {
case result := <-read:
timer.Stop()
return result.h, result.e
case <-timer.C:
return nil, ErrNoProxyProtocol
}
}
+206
View File
@@ -0,0 +1,206 @@
package proxyproto
import (
"fmt"
"net"
"strings"
)
// PolicyFunc can be used to decide whether to trust the PROXY info from
// upstream. If set, the connecting address is passed in as an argument.
//
// See below for the different policies.
//
// In case an error is returned the connection is denied.
type PolicyFunc func(upstream net.Addr) (Policy, error)
// ConnPolicyFunc can be used to decide whether to trust the PROXY info
// based on connection policy options. If set, the connecting addresses
// (remote and local) are passed in as argument.
//
// See below for the different policies.
//
// In case an error is returned the connection is denied.
type ConnPolicyFunc func(connPolicyOptions ConnPolicyOptions) (Policy, error)
// ConnPolicyOptions contains the remote and local addresses of a connection.
type ConnPolicyOptions struct {
Upstream net.Addr
Downstream net.Addr
}
// Policy defines how a connection with a PROXY header address is treated.
type Policy int
const (
// USE address from PROXY header
USE Policy = iota
// IGNORE address from PROXY header, but accept connection
IGNORE
// REJECT connection when PROXY header is sent
// Note: even though the first read on the connection returns an error if
// a PROXY header is present, subsequent reads do not. It is the task of
// the code using the connection to handle that case properly.
REJECT
// REQUIRE connection to send PROXY header, reject if not present
// Note: even though the first read on the connection returns an error if
// a PROXY header is not present, subsequent reads do not. It is the task
// of the code using the connection to handle that case properly.
REQUIRE
// SKIP accepts a connection without requiring the PROXY header
// Note: an example usage can be found in the SkipProxyHeaderForCIDR
// function.
SKIP
)
// SkipProxyHeaderForCIDR returns a PolicyFunc which can be used to accept a
// connection from a skipHeaderCIDR without requiring a PROXY header, e.g.
// Kubernetes pods local traffic. The def is a policy to use when an upstream
// address doesn't match the skipHeaderCIDR.
func SkipProxyHeaderForCIDR(skipHeaderCIDR *net.IPNet, def Policy) PolicyFunc {
return func(upstream net.Addr) (Policy, error) {
ip, err := ipFromAddr(upstream)
if err != nil {
return def, err
}
if skipHeaderCIDR != nil && skipHeaderCIDR.Contains(ip) {
return SKIP, nil
}
return def, nil
}
}
// WithPolicy adds given policy to a connection when passed as option to NewConn()
func WithPolicy(p Policy) func(*Conn) {
return func(c *Conn) {
c.ProxyHeaderPolicy = p
}
}
// LaxWhiteListPolicy returns a PolicyFunc which decides whether the
// upstream ip is allowed to send a proxy header based on a list of allowed
// IP addresses and IP ranges. In case upstream IP is not in list the proxy
// header will be ignored. If one of the provided IP addresses or IP ranges
// is invalid it will return an error instead of a PolicyFunc.
func LaxWhiteListPolicy(allowed []string) (PolicyFunc, error) {
allowFrom, err := parse(allowed)
if err != nil {
return nil, err
}
return whitelistPolicy(allowFrom, IGNORE), nil
}
// MustLaxWhiteListPolicy returns a LaxWhiteListPolicy but will panic if one
// of the provided IP addresses or IP ranges is invalid.
func MustLaxWhiteListPolicy(allowed []string) PolicyFunc {
pfunc, err := LaxWhiteListPolicy(allowed)
if err != nil {
panic(err)
}
return pfunc
}
// StrictWhiteListPolicy returns a PolicyFunc which decides whether the
// upstream ip is allowed to send a proxy header based on a list of allowed
// IP addresses and IP ranges. In case upstream IP is not in list reading on
// the connection will be refused on the first read. Please note: subsequent
// reads do not error. It is the task of the code using the connection to
// handle that case properly. If one of the provided IP addresses or IP
// ranges is invalid it will return an error instead of a PolicyFunc.
func StrictWhiteListPolicy(allowed []string) (PolicyFunc, error) {
allowFrom, err := parse(allowed)
if err != nil {
return nil, err
}
return whitelistPolicy(allowFrom, REJECT), nil
}
// MustStrictWhiteListPolicy returns a StrictWhiteListPolicy but will panic
// if one of the provided IP addresses or IP ranges is invalid.
func MustStrictWhiteListPolicy(allowed []string) PolicyFunc {
pfunc, err := StrictWhiteListPolicy(allowed)
if err != nil {
panic(err)
}
return pfunc
}
func whitelistPolicy(allowed []func(net.IP) bool, def Policy) PolicyFunc {
return func(upstream net.Addr) (Policy, error) {
upstreamIP, err := ipFromAddr(upstream)
if err != nil {
// something is wrong with the source IP, better reject the connection
return REJECT, err
}
for _, allowFrom := range allowed {
if allowFrom(upstreamIP) {
return USE, nil
}
}
return def, nil
}
}
func parse(allowed []string) ([]func(net.IP) bool, error) {
a := make([]func(net.IP) bool, len(allowed))
for i, allowFrom := range allowed {
if strings.LastIndex(allowFrom, "/") > 0 {
_, ipRange, err := net.ParseCIDR(allowFrom)
if err != nil {
return nil, fmt.Errorf("proxyproto: given string %q is not a valid IP range: %v", allowFrom, err)
}
a[i] = ipRange.Contains
} else {
allowed := net.ParseIP(allowFrom)
if allowed == nil {
return nil, fmt.Errorf("proxyproto: given string %q is not a valid IP address", allowFrom)
}
a[i] = allowed.Equal
}
}
return a, nil
}
func ipFromAddr(upstream net.Addr) (net.IP, error) {
upstreamString, _, err := net.SplitHostPort(upstream.String())
if err != nil {
return nil, err
}
upstreamIP := net.ParseIP(upstreamString)
if nil == upstreamIP {
return nil, fmt.Errorf("proxyproto: invalid IP address")
}
return upstreamIP, nil
}
// IgnoreProxyHeaderNotOnInterface retuns a ConnPolicyFunc which can be used to
// decide whether to use or ignore PROXY headers depending on the connection
// being made on a specific interface. This policy can be used when the server
// is bound to multiple interfaces but wants to allow on only one interface.
func IgnoreProxyHeaderNotOnInterface(allowedIP net.IP) ConnPolicyFunc {
return func(connOpts ConnPolicyOptions) (Policy, error) {
ip, err := ipFromAddr(connOpts.Downstream)
if err != nil {
return REJECT, err
}
if allowedIP.Equal(ip) {
return USE, nil
}
return IGNORE, nil
}
}
+389
View File
@@ -0,0 +1,389 @@
package proxyproto
import (
"bufio"
"errors"
"fmt"
"io"
"net"
"sync"
"sync/atomic"
"time"
)
var (
// DefaultReadHeaderTimeout is how long header processing waits for header to
// be read from the wire, if Listener.ReaderHeaderTimeout is not set.
// It's kept as a global variable so to make it easier to find and override,
// e.g. go build -ldflags -X "github.com/pires/go-proxyproto.DefaultReadHeaderTimeout=1s"
DefaultReadHeaderTimeout = 10 * time.Second
// ErrInvalidUpstream should be returned when an upstream connection address
// is not trusted, and therefore is invalid.
ErrInvalidUpstream = fmt.Errorf("proxyproto: upstream connection address not trusted for PROXY information")
)
// Listener is used to wrap an underlying listener,
// whose connections may be using the HAProxy Proxy Protocol.
// If the connection is using the protocol, the RemoteAddr() will return
// the correct client address. ReadHeaderTimeout will be applied to all
// connections in order to prevent blocking operations. If no ReadHeaderTimeout
// is set, a default of 10s will be used. This can be disabled by setting the
// timeout to < 0.
//
// Only one of Policy or ConnPolicy should be provided. If both are provided then
// a panic would occur during accept.
type Listener struct {
Listener net.Listener
// Deprecated: use ConnPolicyFunc instead. This will be removed in future release.
Policy PolicyFunc
ConnPolicy ConnPolicyFunc
ValidateHeader Validator
ReadHeaderTimeout time.Duration
}
// Conn is used to wrap and underlying connection which
// may be speaking the Proxy Protocol. If it is, the RemoteAddr() will
// return the address of the client instead of the proxy address. Each connection
// will have its own readHeaderTimeout and readDeadline set by the Accept() call.
type Conn struct {
readDeadline atomic.Value // time.Time
once sync.Once
readErr error
conn net.Conn
bufReader *bufio.Reader
reader io.Reader
header *Header
ProxyHeaderPolicy Policy
Validate Validator
readHeaderTimeout time.Duration
}
// Validator receives a header and decides whether it is a valid one
// In case the header is not deemed valid it should return an error.
type Validator func(*Header) error
// ValidateHeader adds given validator for proxy headers to a connection when passed as option to NewConn()
func ValidateHeader(v Validator) func(*Conn) {
return func(c *Conn) {
if v != nil {
c.Validate = v
}
}
}
// SetReadHeaderTimeout sets the readHeaderTimeout for a connection when passed as option to NewConn()
func SetReadHeaderTimeout(t time.Duration) func(*Conn) {
return func(c *Conn) {
if t >= 0 {
c.readHeaderTimeout = t
}
}
}
// Accept waits for and returns the next valid connection to the listener.
func (p *Listener) Accept() (net.Conn, error) {
for {
// Get the underlying connection
conn, err := p.Listener.Accept()
if err != nil {
return nil, err
}
proxyHeaderPolicy := USE
if p.Policy != nil && p.ConnPolicy != nil {
panic("only one of policy or connpolicy must be provided.")
}
if p.Policy != nil || p.ConnPolicy != nil {
if p.Policy != nil {
proxyHeaderPolicy, err = p.Policy(conn.RemoteAddr())
} else {
proxyHeaderPolicy, err = p.ConnPolicy(ConnPolicyOptions{
Upstream: conn.RemoteAddr(),
Downstream: conn.LocalAddr(),
})
}
if err != nil {
// can't decide the policy, we can't accept the connection
conn.Close()
if errors.Is(err, ErrInvalidUpstream) {
// keep listening for other connections
continue
}
return nil, err
}
// Handle a connection as a regular one
if proxyHeaderPolicy == SKIP {
return conn, nil
}
}
newConn := NewConn(
conn,
WithPolicy(proxyHeaderPolicy),
ValidateHeader(p.ValidateHeader),
)
// If the ReadHeaderTimeout for the listener is unset, use the default timeout.
if p.ReadHeaderTimeout == 0 {
p.ReadHeaderTimeout = DefaultReadHeaderTimeout
}
// Set the readHeaderTimeout of the new conn to the value of the listener
newConn.readHeaderTimeout = p.ReadHeaderTimeout
return newConn, nil
}
}
// Close closes the underlying listener.
func (p *Listener) Close() error {
return p.Listener.Close()
}
// Addr returns the underlying listener's network address.
func (p *Listener) Addr() net.Addr {
return p.Listener.Addr()
}
// NewConn is used to wrap a net.Conn that may be speaking
// the proxy protocol into a proxyproto.Conn
func NewConn(conn net.Conn, opts ...func(*Conn)) *Conn {
// For v1 the header length is at most 108 bytes.
// For v2 the header length is at most 52 bytes plus the length of the TLVs.
// We use 256 bytes to be safe.
const bufSize = 256
br := bufio.NewReaderSize(conn, bufSize)
pConn := &Conn{
bufReader: br,
reader: io.MultiReader(br, conn),
conn: conn,
}
for _, opt := range opts {
opt(pConn)
}
return pConn
}
// Read is check for the proxy protocol header when doing
// the initial scan. If there is an error parsing the header,
// it is returned and the socket is closed.
func (p *Conn) Read(b []byte) (int, error) {
p.once.Do(func() {
p.readErr = p.readHeader()
})
if p.readErr != nil {
return 0, p.readErr
}
return p.reader.Read(b)
}
// Write wraps original conn.Write
func (p *Conn) Write(b []byte) (int, error) {
return p.conn.Write(b)
}
// Close wraps original conn.Close
func (p *Conn) Close() error {
return p.conn.Close()
}
// ProxyHeader returns the proxy protocol header, if any. If an error occurs
// while reading the proxy header, nil is returned.
func (p *Conn) ProxyHeader() *Header {
p.once.Do(func() { p.readErr = p.readHeader() })
return p.header
}
// LocalAddr returns the address of the server if the proxy
// protocol is being used, otherwise just returns the address of
// the socket server. In case an error happens on reading the
// proxy header the original LocalAddr is returned, not the one
// from the proxy header even if the proxy header itself is
// syntactically correct.
func (p *Conn) LocalAddr() net.Addr {
p.once.Do(func() { p.readErr = p.readHeader() })
if p.header == nil || p.header.Command.IsLocal() || p.readErr != nil {
return p.conn.LocalAddr()
}
return p.header.DestinationAddr
}
// RemoteAddr returns the address of the client if the proxy
// protocol is being used, otherwise just returns the address of
// the socket peer. In case an error happens on reading the
// proxy header the original RemoteAddr is returned, not the one
// from the proxy header even if the proxy header itself is
// syntactically correct.
func (p *Conn) RemoteAddr() net.Addr {
p.once.Do(func() { p.readErr = p.readHeader() })
if p.header == nil || p.header.Command.IsLocal() || p.readErr != nil {
return p.conn.RemoteAddr()
}
return p.header.SourceAddr
}
// Raw returns the underlying connection which can be casted to
// a concrete type, allowing access to specialized functions.
//
// Use this ONLY if you know exactly what you are doing.
func (p *Conn) Raw() net.Conn {
return p.conn
}
// TCPConn returns the underlying TCP connection,
// allowing access to specialized functions.
//
// Use this ONLY if you know exactly what you are doing.
func (p *Conn) TCPConn() (conn *net.TCPConn, ok bool) {
conn, ok = p.conn.(*net.TCPConn)
return
}
// UnixConn returns the underlying Unix socket connection,
// allowing access to specialized functions.
//
// Use this ONLY if you know exactly what you are doing.
func (p *Conn) UnixConn() (conn *net.UnixConn, ok bool) {
conn, ok = p.conn.(*net.UnixConn)
return
}
// UDPConn returns the underlying UDP connection,
// allowing access to specialized functions.
//
// Use this ONLY if you know exactly what you are doing.
func (p *Conn) UDPConn() (conn *net.UDPConn, ok bool) {
conn, ok = p.conn.(*net.UDPConn)
return
}
// SetDeadline wraps original conn.SetDeadline
func (p *Conn) SetDeadline(t time.Time) error {
p.readDeadline.Store(t)
return p.conn.SetDeadline(t)
}
// SetReadDeadline wraps original conn.SetReadDeadline
func (p *Conn) SetReadDeadline(t time.Time) error {
// Set a local var that tells us the desired deadline. This is
// needed in order to reset the read deadline to the one that is
// desired by the user, rather than an empty deadline.
p.readDeadline.Store(t)
return p.conn.SetReadDeadline(t)
}
// SetWriteDeadline wraps original conn.SetWriteDeadline
func (p *Conn) SetWriteDeadline(t time.Time) error {
return p.conn.SetWriteDeadline(t)
}
func (p *Conn) readHeader() error {
// If the connection's readHeaderTimeout is more than 0,
// push our deadline back to now plus the timeout. This should only
// run on the connection, as we don't want to override the previous
// read deadline the user may have used.
if p.readHeaderTimeout > 0 {
if err := p.conn.SetReadDeadline(time.Now().Add(p.readHeaderTimeout)); err != nil {
return err
}
}
header, err := Read(p.bufReader)
// If the connection's readHeaderTimeout is more than 0, undo the change to the
// deadline that we made above. Because we retain the readDeadline as part of our
// SetReadDeadline override, we know the user's desired deadline so we use that.
// Therefore, we check whether the error is a net.Timeout and if it is, we decide
// the proxy proto does not exist and set the error accordingly.
if p.readHeaderTimeout > 0 {
t := p.readDeadline.Load()
if t == nil {
t = time.Time{}
}
if err := p.conn.SetReadDeadline(t.(time.Time)); err != nil {
return err
}
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
err = ErrNoProxyProtocol
}
}
// For the purpose of this wrapper shamefully stolen from armon/go-proxyproto
// let's act as if there was no error when PROXY protocol is not present.
if err == ErrNoProxyProtocol {
// but not if it is required that the connection has one
if p.ProxyHeaderPolicy == REQUIRE {
return err
}
return nil
}
// proxy protocol header was found
if err == nil && header != nil {
switch p.ProxyHeaderPolicy {
case REJECT:
// this connection is not allowed to send one
return ErrSuperfluousProxyHeader
case USE, REQUIRE:
if p.Validate != nil {
err = p.Validate(header)
if err != nil {
return err
}
}
p.header = header
}
}
return err
}
// ReadFrom implements the io.ReaderFrom ReadFrom method
func (p *Conn) ReadFrom(r io.Reader) (int64, error) {
if rf, ok := p.conn.(io.ReaderFrom); ok {
return rf.ReadFrom(r)
}
return io.Copy(p.conn, r)
}
// WriteTo implements io.WriterTo
func (p *Conn) WriteTo(w io.Writer) (int64, error) {
p.once.Do(func() { p.readErr = p.readHeader() })
if p.readErr != nil {
return 0, p.readErr
}
b := make([]byte, p.bufReader.Buffered())
if _, err := p.bufReader.Read(b); err != nil {
return 0, err // this should never as we read buffered data
}
var n int64
{
nn, err := w.Write(b)
n += int64(nn)
if err != nil {
return n, err
}
}
{
nn, err := io.Copy(w, p.conn)
n += nn
if err != nil {
return n, err
}
}
return n, nil
}
+132
View File
@@ -0,0 +1,132 @@
// Type-Length-Value splitting and parsing for proxy protocol V2
// See spec https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt sections 2.2 to 2.7 and
package proxyproto
import (
"encoding/binary"
"errors"
"fmt"
"math"
)
const (
// Section 2.2
PP2_TYPE_ALPN PP2Type = 0x01
PP2_TYPE_AUTHORITY PP2Type = 0x02
PP2_TYPE_CRC32C PP2Type = 0x03
PP2_TYPE_NOOP PP2Type = 0x04
PP2_TYPE_UNIQUE_ID PP2Type = 0x05
PP2_TYPE_SSL PP2Type = 0x20
PP2_SUBTYPE_SSL_VERSION PP2Type = 0x21
PP2_SUBTYPE_SSL_CN PP2Type = 0x22
PP2_SUBTYPE_SSL_CIPHER PP2Type = 0x23
PP2_SUBTYPE_SSL_SIG_ALG PP2Type = 0x24
PP2_SUBTYPE_SSL_KEY_ALG PP2Type = 0x25
PP2_TYPE_NETNS PP2Type = 0x30
// Section 2.2.7, reserved types
PP2_TYPE_MIN_CUSTOM PP2Type = 0xE0
PP2_TYPE_MAX_CUSTOM PP2Type = 0xEF
PP2_TYPE_MIN_EXPERIMENT PP2Type = 0xF0
PP2_TYPE_MAX_EXPERIMENT PP2Type = 0xF7
PP2_TYPE_MIN_FUTURE PP2Type = 0xF8
PP2_TYPE_MAX_FUTURE PP2Type = 0xFF
)
var (
ErrTruncatedTLV = errors.New("proxyproto: truncated TLV")
ErrMalformedTLV = errors.New("proxyproto: malformed TLV Value")
ErrIncompatibleTLV = errors.New("proxyproto: incompatible TLV type")
)
// PP2Type is the proxy protocol v2 type
type PP2Type byte
// TLV is a uninterpreted Type-Length-Value for V2 protocol, see section 2.2
type TLV struct {
Type PP2Type
Value []byte
}
// SplitTLVs splits the Type-Length-Value vector, returns the vector or an error.
func SplitTLVs(raw []byte) ([]TLV, error) {
var tlvs []TLV
for i := 0; i < len(raw); {
tlv := TLV{
Type: PP2Type(raw[i]),
}
if len(raw)-i <= 2 {
return nil, ErrTruncatedTLV
}
tlvLen := int(binary.BigEndian.Uint16(raw[i+1 : i+3])) // Max length = 65K
i += 3
if i+tlvLen > len(raw) {
return nil, ErrTruncatedTLV
}
// Ignore no-op padding
if tlv.Type != PP2_TYPE_NOOP {
tlv.Value = make([]byte, tlvLen)
copy(tlv.Value, raw[i:i+tlvLen])
}
i += tlvLen
tlvs = append(tlvs, tlv)
}
return tlvs, nil
}
// JoinTLVs joins multiple Type-Length-Value records.
func JoinTLVs(tlvs []TLV) ([]byte, error) {
var raw []byte
for _, tlv := range tlvs {
if len(tlv.Value) > math.MaxUint16 {
return nil, fmt.Errorf("proxyproto: cannot format TLV %v with length %d", tlv.Type, len(tlv.Value))
}
var length [2]byte
binary.BigEndian.PutUint16(length[:], uint16(len(tlv.Value)))
raw = append(raw, byte(tlv.Type))
raw = append(raw, length[:]...)
raw = append(raw, tlv.Value...)
}
return raw, nil
}
// Registered is true if the type is registered in the spec, see section 2.2
func (p PP2Type) Registered() bool {
switch p {
case PP2_TYPE_ALPN,
PP2_TYPE_AUTHORITY,
PP2_TYPE_CRC32C,
PP2_TYPE_NOOP,
PP2_TYPE_UNIQUE_ID,
PP2_TYPE_SSL,
PP2_SUBTYPE_SSL_VERSION,
PP2_SUBTYPE_SSL_CN,
PP2_SUBTYPE_SSL_CIPHER,
PP2_SUBTYPE_SSL_SIG_ALG,
PP2_SUBTYPE_SSL_KEY_ALG,
PP2_TYPE_NETNS:
return true
}
return false
}
// App is true if the type is reserved for application specific data, see section 2.2.7
func (p PP2Type) App() bool {
return p >= PP2_TYPE_MIN_CUSTOM && p <= PP2_TYPE_MAX_CUSTOM
}
// Experiment is true if the type is reserved for temporary experimental use by application developers, see section 2.2.7
func (p PP2Type) Experiment() bool {
return p >= PP2_TYPE_MIN_EXPERIMENT && p <= PP2_TYPE_MAX_EXPERIMENT
}
// Future is true is the type is reserved for future use, see section 2.2.7
func (p PP2Type) Future() bool {
return p >= PP2_TYPE_MIN_FUTURE
}
// Spec is true if the type is covered by the spec, see section 2.2 and 2.2.7
func (p PP2Type) Spec() bool {
return p.Registered() || p.App() || p.Experiment() || p.Future()
}
+243
View File
@@ -0,0 +1,243 @@
package proxyproto
import (
"bufio"
"bytes"
"fmt"
"net"
"net/netip"
"strconv"
"strings"
)
const (
crlf = "\r\n"
separator = " "
)
func initVersion1() *Header {
header := new(Header)
header.Version = 1
// Command doesn't exist in v1
header.Command = PROXY
return header
}
func parseVersion1(reader *bufio.Reader) (*Header, error) {
//The header cannot be more than 107 bytes long. Per spec:
//
// (...)
// - worst case (optional fields set to 0xff) :
// "PROXY UNKNOWN ffff:f...f:ffff ffff:f...f:ffff 65535 65535\r\n"
// => 5 + 1 + 7 + 1 + 39 + 1 + 39 + 1 + 5 + 1 + 5 + 2 = 107 chars
//
// So a 108-byte buffer is always enough to store all the line and a
// trailing zero for string processing.
//
// It must also be CRLF terminated, as above. The header does not otherwise
// contain a CR or LF byte.
//
// ISSUE #69
// We can't use Peek here as it will block trying to fill the buffer, which
// will never happen if the header is TCP4 or TCP6 (max. 56 and 104 bytes
// respectively) and the server is expected to speak first.
//
// Similarly, we can't use ReadString or ReadBytes as these will keep reading
// until the delimiter is found; an abusive client could easily disrupt a
// server by sending a large amount of data that do not contain a LF byte.
// Another means of attack would be to start connections and simply not send
// data after the initial PROXY signature bytes, accumulating a large
// number of blocked goroutines on the server. ReadSlice will also block for
// a delimiter when the internal buffer does not fill up.
//
// A plain Read is also problematic since we risk reading past the end of the
// header without being able to easily put the excess bytes back into the reader's
// buffer (with the current implementation's design).
//
// So we use a ReadByte loop, which solves the overflow problem and avoids
// reading beyond the end of the header. However, we need one more trick to harden
// against partial header attacks (slow loris) - per spec:
//
// (..) The sender must always ensure that the header is sent at once, so that
// the transport layer maintains atomicity along the path to the receiver. The
// receiver may be tolerant to partial headers or may simply drop the connection
// when receiving a partial header. Recommendation is to be tolerant, but
// implementation constraints may not always easily permit this.
//
// We are subject to such implementation constraints. So we return an error if
// the header cannot be fully extracted with a single read of the underlying
// reader.
buf := make([]byte, 0, 107)
for {
b, err := reader.ReadByte()
if err != nil {
return nil, fmt.Errorf(ErrCantReadVersion1Header.Error()+": %v", err)
}
buf = append(buf, b)
if b == '\n' {
// End of header found
break
}
if len(buf) == 107 {
// No delimiter in first 107 bytes
return nil, ErrVersion1HeaderTooLong
}
if reader.Buffered() == 0 {
// Header was not buffered in a single read. Since we can't
// differentiate between genuine slow writers and DoS agents,
// we abort. On healthy networks, this should never happen.
return nil, ErrCantReadVersion1Header
}
}
// Check for CR before LF.
if len(buf) < 2 || buf[len(buf)-2] != '\r' {
return nil, ErrLineMustEndWithCrlf
}
// Check full signature.
tokens := strings.Split(string(buf[:len(buf)-2]), separator)
// Expect at least 2 tokens: "PROXY" and the transport protocol.
if len(tokens) < 2 {
return nil, ErrCantReadAddressFamilyAndProtocol
}
// Read address family and protocol
var transportProtocol AddressFamilyAndProtocol
switch tokens[1] {
case "TCP4":
transportProtocol = TCPv4
case "TCP6":
transportProtocol = TCPv6
case "UNKNOWN":
transportProtocol = UNSPEC // doesn't exist in v1 but fits UNKNOWN
default:
return nil, ErrCantReadAddressFamilyAndProtocol
}
// Expect 6 tokens only when UNKNOWN is not present.
if transportProtocol != UNSPEC && len(tokens) < 6 {
return nil, ErrCantReadAddressFamilyAndProtocol
}
// When a signature is found, allocate a v1 header with Command set to PROXY.
// Command doesn't exist in v1 but set it for other parts of this library
// to rely on it for determining connection details.
header := initVersion1()
// Transport protocol has been processed already.
header.TransportProtocol = transportProtocol
// When UNKNOWN, set the command to LOCAL and return early
if header.TransportProtocol == UNSPEC {
header.Command = LOCAL
return header, nil
}
// Otherwise, continue to read addresses and ports
sourceIP, err := parseV1IPAddress(header.TransportProtocol, tokens[2])
if err != nil {
return nil, err
}
destIP, err := parseV1IPAddress(header.TransportProtocol, tokens[3])
if err != nil {
return nil, err
}
sourcePort, err := parseV1PortNumber(tokens[4])
if err != nil {
return nil, err
}
destPort, err := parseV1PortNumber(tokens[5])
if err != nil {
return nil, err
}
header.SourceAddr = &net.TCPAddr{
IP: sourceIP,
Port: sourcePort,
}
header.DestinationAddr = &net.TCPAddr{
IP: destIP,
Port: destPort,
}
return header, nil
}
func (header *Header) formatVersion1() ([]byte, error) {
// As of version 1, only "TCP4" ( \x54 \x43 \x50 \x34 ) for TCP over IPv4,
// and "TCP6" ( \x54 \x43 \x50 \x36 ) for TCP over IPv6 are allowed.
var proto string
switch header.TransportProtocol {
case TCPv4:
proto = "TCP4"
case TCPv6:
proto = "TCP6"
default:
// Unknown connection (short form)
return []byte("PROXY UNKNOWN" + crlf), nil
}
sourceAddr, sourceOK := header.SourceAddr.(*net.TCPAddr)
destAddr, destOK := header.DestinationAddr.(*net.TCPAddr)
if !sourceOK || !destOK {
return nil, ErrInvalidAddress
}
sourceIP, destIP := sourceAddr.IP, destAddr.IP
switch header.TransportProtocol {
case TCPv4:
sourceIP = sourceIP.To4()
destIP = destIP.To4()
case TCPv6:
sourceIP = sourceIP.To16()
destIP = destIP.To16()
}
if sourceIP == nil || destIP == nil {
return nil, ErrInvalidAddress
}
buf := bytes.NewBuffer(make([]byte, 0, 108))
buf.Write(SIGV1)
buf.WriteString(separator)
buf.WriteString(proto)
buf.WriteString(separator)
buf.WriteString(sourceIP.String())
buf.WriteString(separator)
buf.WriteString(destIP.String())
buf.WriteString(separator)
buf.WriteString(strconv.Itoa(sourceAddr.Port))
buf.WriteString(separator)
buf.WriteString(strconv.Itoa(destAddr.Port))
buf.WriteString(crlf)
return buf.Bytes(), nil
}
func parseV1PortNumber(portStr string) (int, error) {
port, err := strconv.Atoi(portStr)
if err != nil || port < 0 || port > 65535 {
return 0, ErrInvalidPortNumber
}
return port, nil
}
func parseV1IPAddress(protocol AddressFamilyAndProtocol, addrStr string) (net.IP, error) {
addr, err := netip.ParseAddr(addrStr)
if err != nil {
return nil, ErrInvalidAddress
}
switch protocol {
case TCPv4:
if addr.Is4() {
return net.IP(addr.AsSlice()), nil
}
case TCPv6:
if addr.Is6() || addr.Is4In6() {
return net.IP(addr.AsSlice()), nil
}
}
return nil, ErrInvalidAddress
}
+285
View File
@@ -0,0 +1,285 @@
package proxyproto
import (
"bufio"
"bytes"
"encoding/binary"
"errors"
"io"
"net"
)
var (
lengthUnspec = uint16(0)
lengthV4 = uint16(12)
lengthV6 = uint16(36)
lengthUnix = uint16(216)
lengthUnspecBytes = func() []byte {
a := make([]byte, 2)
binary.BigEndian.PutUint16(a, lengthUnspec)
return a
}()
lengthV4Bytes = func() []byte {
a := make([]byte, 2)
binary.BigEndian.PutUint16(a, lengthV4)
return a
}()
lengthV6Bytes = func() []byte {
a := make([]byte, 2)
binary.BigEndian.PutUint16(a, lengthV6)
return a
}()
lengthUnixBytes = func() []byte {
a := make([]byte, 2)
binary.BigEndian.PutUint16(a, lengthUnix)
return a
}()
errUint16Overflow = errors.New("proxyproto: uint16 overflow")
)
type _ports struct {
SrcPort uint16
DstPort uint16
}
type _addr4 struct {
Src [4]byte
Dst [4]byte
SrcPort uint16
DstPort uint16
}
type _addr6 struct {
Src [16]byte
Dst [16]byte
_ports
}
type _addrUnix struct {
Src [108]byte
Dst [108]byte
}
func parseVersion2(reader *bufio.Reader) (header *Header, err error) {
// Skip first 12 bytes (signature)
for i := 0; i < 12; i++ {
if _, err = reader.ReadByte(); err != nil {
return nil, ErrCantReadProtocolVersionAndCommand
}
}
header = new(Header)
header.Version = 2
// Read the 13th byte, protocol version and command
b13, err := reader.ReadByte()
if err != nil {
return nil, ErrCantReadProtocolVersionAndCommand
}
header.Command = ProtocolVersionAndCommand(b13)
if _, ok := supportedCommand[header.Command]; !ok {
return nil, ErrUnsupportedProtocolVersionAndCommand
}
// Read the 14th byte, address family and protocol
b14, err := reader.ReadByte()
if err != nil {
return nil, ErrCantReadAddressFamilyAndProtocol
}
header.TransportProtocol = AddressFamilyAndProtocol(b14)
// UNSPEC is only supported when LOCAL is set.
if header.TransportProtocol == UNSPEC && header.Command != LOCAL {
return nil, ErrUnsupportedAddressFamilyAndProtocol
}
// Make sure there are bytes available as specified in length
var length uint16
if err := binary.Read(io.LimitReader(reader, 2), binary.BigEndian, &length); err != nil {
return nil, ErrCantReadLength
}
if !header.validateLength(length) {
return nil, ErrInvalidLength
}
// Return early if the length is zero, which means that
// there's no address information and TLVs present for UNSPEC.
if length == 0 {
return header, nil
}
if _, err := reader.Peek(int(length)); err != nil {
return nil, ErrInvalidLength
}
// Length-limited reader for payload section
payloadReader := io.LimitReader(reader, int64(length)).(*io.LimitedReader)
// Read addresses and ports for protocols other than UNSPEC.
// Ignore address information for UNSPEC, and skip straight to read TLVs,
// since the length is greater than zero.
if header.TransportProtocol != UNSPEC {
if header.TransportProtocol.IsIPv4() {
var addr _addr4
if err := binary.Read(payloadReader, binary.BigEndian, &addr); err != nil {
return nil, ErrInvalidAddress
}
header.SourceAddr = newIPAddr(header.TransportProtocol, addr.Src[:], addr.SrcPort)
header.DestinationAddr = newIPAddr(header.TransportProtocol, addr.Dst[:], addr.DstPort)
} else if header.TransportProtocol.IsIPv6() {
var addr _addr6
if err := binary.Read(payloadReader, binary.BigEndian, &addr); err != nil {
return nil, ErrInvalidAddress
}
header.SourceAddr = newIPAddr(header.TransportProtocol, addr.Src[:], addr.SrcPort)
header.DestinationAddr = newIPAddr(header.TransportProtocol, addr.Dst[:], addr.DstPort)
} else if header.TransportProtocol.IsUnix() {
var addr _addrUnix
if err := binary.Read(payloadReader, binary.BigEndian, &addr); err != nil {
return nil, ErrInvalidAddress
}
network := "unix"
if header.TransportProtocol.IsDatagram() {
network = "unixgram"
}
header.SourceAddr = &net.UnixAddr{
Net: network,
Name: parseUnixName(addr.Src[:]),
}
header.DestinationAddr = &net.UnixAddr{
Net: network,
Name: parseUnixName(addr.Dst[:]),
}
}
}
// Copy bytes for optional Type-Length-Value vector
header.rawTLVs = make([]byte, payloadReader.N) // Allocate minimum size slice
if _, err = io.ReadFull(payloadReader, header.rawTLVs); err != nil && err != io.EOF {
return nil, err
}
return header, nil
}
func (header *Header) formatVersion2() ([]byte, error) {
var buf bytes.Buffer
buf.Write(SIGV2)
buf.WriteByte(header.Command.toByte())
buf.WriteByte(header.TransportProtocol.toByte())
if header.TransportProtocol.IsUnspec() {
// For UNSPEC, write no addresses and ports but only TLVs if they are present
hdrLen, err := addTLVLen(lengthUnspecBytes, len(header.rawTLVs))
if err != nil {
return nil, err
}
buf.Write(hdrLen)
} else {
var addrSrc, addrDst []byte
if header.TransportProtocol.IsIPv4() {
hdrLen, err := addTLVLen(lengthV4Bytes, len(header.rawTLVs))
if err != nil {
return nil, err
}
buf.Write(hdrLen)
sourceIP, destIP, _ := header.IPs()
addrSrc = sourceIP.To4()
addrDst = destIP.To4()
} else if header.TransportProtocol.IsIPv6() {
hdrLen, err := addTLVLen(lengthV6Bytes, len(header.rawTLVs))
if err != nil {
return nil, err
}
buf.Write(hdrLen)
sourceIP, destIP, _ := header.IPs()
addrSrc = sourceIP.To16()
addrDst = destIP.To16()
} else if header.TransportProtocol.IsUnix() {
buf.Write(lengthUnixBytes)
sourceAddr, destAddr, ok := header.UnixAddrs()
if !ok {
return nil, ErrInvalidAddress
}
addrSrc = formatUnixName(sourceAddr.Name)
addrDst = formatUnixName(destAddr.Name)
}
if addrSrc == nil || addrDst == nil {
return nil, ErrInvalidAddress
}
buf.Write(addrSrc)
buf.Write(addrDst)
if sourcePort, destPort, ok := header.Ports(); ok {
portBytes := make([]byte, 2)
binary.BigEndian.PutUint16(portBytes, uint16(sourcePort))
buf.Write(portBytes)
binary.BigEndian.PutUint16(portBytes, uint16(destPort))
buf.Write(portBytes)
}
}
if len(header.rawTLVs) > 0 {
buf.Write(header.rawTLVs)
}
return buf.Bytes(), nil
}
func (header *Header) validateLength(length uint16) bool {
if header.TransportProtocol.IsIPv4() {
return length >= lengthV4
} else if header.TransportProtocol.IsIPv6() {
return length >= lengthV6
} else if header.TransportProtocol.IsUnix() {
return length >= lengthUnix
} else if header.TransportProtocol.IsUnspec() {
return length >= lengthUnspec
}
return false
}
// addTLVLen adds the length of the TLV to the header length or errors on uint16 overflow.
func addTLVLen(cur []byte, tlvLen int) ([]byte, error) {
if tlvLen == 0 {
return cur, nil
}
curLen := binary.BigEndian.Uint16(cur)
newLen := int(curLen) + tlvLen
if newLen >= 1<<16 {
return nil, errUint16Overflow
}
a := make([]byte, 2)
binary.BigEndian.PutUint16(a, uint16(newLen))
return a, nil
}
func newIPAddr(transport AddressFamilyAndProtocol, ip net.IP, port uint16) net.Addr {
if transport.IsStream() {
return &net.TCPAddr{IP: ip, Port: int(port)}
} else if transport.IsDatagram() {
return &net.UDPAddr{IP: ip, Port: int(port)}
} else {
return nil
}
}
func parseUnixName(b []byte) string {
i := bytes.IndexByte(b, 0)
if i < 0 {
return string(b)
}
return string(b[:i])
}
func formatUnixName(name string) []byte {
n := int(lengthUnix) / 2
if len(name) >= n {
return []byte(name[:n])
}
pad := make([]byte, n-len(name))
return append([]byte(name), pad...)
}
+47
View File
@@ -0,0 +1,47 @@
package proxyproto
// ProtocolVersionAndCommand represents the command in proxy protocol v2.
// Command doesn't exist in v1 but it should be set since other parts of
// this library may rely on it for determining connection details.
type ProtocolVersionAndCommand byte
const (
// LOCAL represents the LOCAL command in v2 or UNKNOWN transport in v1,
// in which case no address information is expected.
LOCAL ProtocolVersionAndCommand = '\x20'
// PROXY represents the PROXY command in v2 or transport is not UNKNOWN in v1,
// in which case valid local/remote address and port information is expected.
PROXY ProtocolVersionAndCommand = '\x21'
)
var supportedCommand = map[ProtocolVersionAndCommand]bool{
LOCAL: true,
PROXY: true,
}
// IsLocal returns true if the command in v2 is LOCAL or the transport in v1 is UNKNOWN,
// i.e. when no address information is expected, false otherwise.
func (pvc ProtocolVersionAndCommand) IsLocal() bool {
return LOCAL == pvc
}
// IsProxy returns true if the command in v2 is PROXY or the transport in v1 is not UNKNOWN,
// i.e. when valid local/remote address and port information is expected, false otherwise.
func (pvc ProtocolVersionAndCommand) IsProxy() bool {
return PROXY == pvc
}
// IsUnspec returns true if the command is unspecified, false otherwise.
func (pvc ProtocolVersionAndCommand) IsUnspec() bool {
return !(pvc.IsLocal() || pvc.IsProxy())
}
func (pvc ProtocolVersionAndCommand) toByte() byte {
if pvc.IsLocal() {
return byte(LOCAL)
} else if pvc.IsProxy() {
return byte(PROXY)
}
return byte(LOCAL)
}
+3
View File
@@ -60,6 +60,9 @@ func (c *Console) complete(input []rune, pos int) readline.Completions {
comps = comps.Usage("%s", completions.Usage)
comps = c.justifyCommandComps(comps)
// Display extension and alias arguments as a list (one per line)
comps = comps.DisplayList("extension arguments", "alias arguments")
// If any errors arose from the completion call itself.
if err != nil {
comps = readline.CompleteMessage("failed to load config: " + err.Error())
+9 -12
View File
@@ -5,9 +5,7 @@
package dnsmessage
import (
"math"
"slices"
"strings"
)
// An SVCBResource is an SVCB Resource record.
@@ -23,19 +21,18 @@ func (r *SVCBResource) realType() Type {
// GoString implements fmt.GoStringer.GoString.
func (r *SVCBResource) GoString() string {
var b strings.Builder
b.WriteString("dnsmessage.SVCBResource{")
b.WriteString("Priority: " + printUint16(r.Priority) + ", ")
b.WriteString("Target: " + r.Target.GoString() + ", ")
b.WriteString("Params: []dnsmessage.SVCParam{")
b := []byte("dnsmessage.SVCBResource{" +
"Priority: " + printUint16(r.Priority) + ", " +
"Target: " + r.Target.GoString() + ", " +
"Params: []dnsmessage.SVCParam{")
if len(r.Params) > 0 {
b.WriteString(r.Params[0].GoString())
b = append(b, r.Params[0].GoString()...)
for _, p := range r.Params[1:] {
b.WriteString(", " + p.GoString())
b = append(b, ", "+p.GoString()...)
}
}
b.WriteString("}}")
return b.String()
b = append(b, "}}"...)
return string(b)
}
// An HTTPSResource is an HTTPS Resource record.
@@ -176,7 +173,7 @@ func (r *SVCBResource) pack(msg []byte, _ map[string]uint16, _ int) ([]byte, err
if i > 0 && param.Key <= previousKey {
return oldMsg, &nestedError{"SVCBResource.Params", errParamOutOfOrder}
}
if len(param.Value) > math.MaxUint16 {
if len(param.Value) > (1<<16)-1 {
return oldMsg, &nestedError{"SVCBResource.Params", errTooLongSVCBValue}
}
msg = packUint16(msg, uint16(param.Key))
+148 -12
View File
@@ -376,11 +376,24 @@ type ClientConn struct {
// completely unresponsive connection.
pendingResets int
// readBeforeStreamID is the smallest stream ID that has not been followed by
// a frame read from the peer. We use this to determine when a request may
// have been sent to a completely unresponsive connection:
// If the request ID is less than readBeforeStreamID, then we have had some
// indication of life on the connection since sending the request.
readBeforeStreamID uint32
// reqHeaderMu is a 1-element semaphore channel controlling access to sending new requests.
// Write to reqHeaderMu to lock it, read from it to unlock.
// Lock reqmu BEFORE mu or wmu.
reqHeaderMu chan struct{}
// internalStateHook reports state changes back to the net/http.ClientConn.
// Note that this is different from the user state hook registered by
// net/http.ClientConn.SetStateHook: The internal hook calls ClientConn,
// which calls the user hook.
internalStateHook func()
// wmu is held while writing.
// Acquire BEFORE mu when holding both, to avoid blocking mu on network writes.
// Only acquire both at the same time when changing peer settings.
@@ -710,7 +723,7 @@ func canRetryError(err error) bool {
func (t *Transport) dialClientConn(ctx context.Context, addr string, singleUse bool) (*ClientConn, error) {
if t.transportTestHooks != nil {
return t.newClientConn(nil, singleUse)
return t.newClientConn(nil, singleUse, nil)
}
host, _, err := net.SplitHostPort(addr)
if err != nil {
@@ -720,7 +733,7 @@ func (t *Transport) dialClientConn(ctx context.Context, addr string, singleUse b
if err != nil {
return nil, err
}
return t.newClientConn(tconn, singleUse)
return t.newClientConn(tconn, singleUse, nil)
}
func (t *Transport) newTLSConfig(host string) *tls.Config {
@@ -772,10 +785,10 @@ func (t *Transport) expectContinueTimeout() time.Duration {
}
func (t *Transport) NewClientConn(c net.Conn) (*ClientConn, error) {
return t.newClientConn(c, t.disableKeepAlives())
return t.newClientConn(c, t.disableKeepAlives(), nil)
}
func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, error) {
func (t *Transport) newClientConn(c net.Conn, singleUse bool, internalStateHook func()) (*ClientConn, error) {
conf := configFromTransport(t)
cc := &ClientConn{
t: t,
@@ -797,6 +810,7 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
pings: make(map[[8]byte]chan struct{}),
reqHeaderMu: make(chan struct{}, 1),
lastActive: time.Now(),
internalStateHook: internalStateHook,
}
if t.transportTestHooks != nil {
t.transportTestHooks.newclientconn(cc)
@@ -1037,10 +1051,7 @@ func (cc *ClientConn) idleStateLocked() (st clientConnIdleState) {
maxConcurrentOkay = cc.currentRequestCountLocked() < int(cc.maxConcurrentStreams)
}
st.canTakeNewRequest = cc.goAway == nil && !cc.closed && !cc.closing && maxConcurrentOkay &&
!cc.doNotReuse &&
int64(cc.nextStreamID)+2*int64(cc.pendingRequests) < math.MaxInt32 &&
!cc.tooIdleLocked()
st.canTakeNewRequest = maxConcurrentOkay && cc.isUsableLocked()
// If this connection has never been used for a request and is closed,
// then let it take a request (which will fail).
@@ -1056,6 +1067,31 @@ func (cc *ClientConn) idleStateLocked() (st clientConnIdleState) {
return
}
func (cc *ClientConn) isUsableLocked() bool {
return cc.goAway == nil &&
!cc.closed &&
!cc.closing &&
!cc.doNotReuse &&
int64(cc.nextStreamID)+2*int64(cc.pendingRequests) < math.MaxInt32 &&
!cc.tooIdleLocked()
}
// canReserveLocked reports whether a net/http.ClientConn can reserve a slot on this conn.
//
// This follows slightly different rules than clientConnIdleState.canTakeNewRequest.
// We only permit reservations up to the conn's concurrency limit.
// This differs from ClientConn.ReserveNewRequest, which permits reservations
// past the limit when StrictMaxConcurrentStreams is set.
func (cc *ClientConn) canReserveLocked() bool {
if cc.currentRequestCountLocked() >= int(cc.maxConcurrentStreams) {
return false
}
if !cc.isUsableLocked() {
return false
}
return true
}
// currentRequestCountLocked reports the number of concurrency slots currently in use,
// including active streams, reserved slots, and reset streams waiting for acknowledgement.
func (cc *ClientConn) currentRequestCountLocked() int {
@@ -1067,6 +1103,14 @@ func (cc *ClientConn) canTakeNewRequestLocked() bool {
return st.canTakeNewRequest
}
// availableLocked reports the number of concurrency slots available.
func (cc *ClientConn) availableLocked() int {
if !cc.canTakeNewRequestLocked() {
return 0
}
return max(0, int(cc.maxConcurrentStreams)-cc.currentRequestCountLocked())
}
// tooIdleLocked reports whether this connection has been been sitting idle
// for too much wall time.
func (cc *ClientConn) tooIdleLocked() bool {
@@ -1091,6 +1135,7 @@ func (cc *ClientConn) closeConn() {
t := time.AfterFunc(250*time.Millisecond, cc.forceCloseConn)
defer t.Stop()
cc.tconn.Close()
cc.maybeCallStateHook()
}
// A tls.Conn.Close can hang for a long time if the peer is unresponsive.
@@ -1616,6 +1661,8 @@ func (cs *clientStream) cleanupWriteRequest(err error) {
}
bodyClosed := cs.reqBodyClosed
closeOnIdle := cc.singleUse || cc.doNotReuse || cc.t.disableKeepAlives() || cc.goAway != nil
// Have we read any frames from the connection since sending this request?
readSinceStream := cc.readBeforeStreamID > cs.ID
cc.mu.Unlock()
if mustCloseBody {
cs.reqBody.Close()
@@ -1647,8 +1694,10 @@ func (cs *clientStream) cleanupWriteRequest(err error) {
//
// This could be due to the server becoming unresponsive.
// To avoid sending too many requests on a dead connection,
// we let the request continue to consume a concurrency slot
// until we can confirm the server is still responding.
// if we haven't read any frames from the connection since
// sending this request, we let it continue to consume
// a concurrency slot until we can confirm the server is
// still responding.
// We do this by sending a PING frame along with the RST_STREAM
// (unless a ping is already in flight).
//
@@ -1659,7 +1708,7 @@ func (cs *clientStream) cleanupWriteRequest(err error) {
// because it's short lived and will probably be closed before
// we get the ping response.
ping := false
if !closeOnIdle {
if !closeOnIdle && !readSinceStream {
cc.mu.Lock()
// rstStreamPingsBlocked works around a gRPC behavior:
// see comment on the field for details.
@@ -1693,6 +1742,7 @@ func (cs *clientStream) cleanupWriteRequest(err error) {
}
close(cs.donec)
cc.maybeCallStateHook()
}
// awaitOpenSlotForStreamLocked waits until len(streams) < maxConcurrentStreams.
@@ -2745,6 +2795,7 @@ func (rl *clientConnReadLoop) streamByID(id uint32, headerOrData bool) *clientSt
// See comment on ClientConn.rstStreamPingsBlocked for details.
rl.cc.rstStreamPingsBlocked = false
}
rl.cc.readBeforeStreamID = rl.cc.nextStreamID
cs := rl.cc.streams[id]
if cs != nil && !cs.readAborted {
return cs
@@ -2795,6 +2846,7 @@ func (rl *clientConnReadLoop) processSettings(f *SettingsFrame) error {
func (rl *clientConnReadLoop) processSettingsNoWrite(f *SettingsFrame) error {
cc := rl.cc
defer cc.maybeCallStateHook()
cc.mu.Lock()
defer cc.mu.Unlock()
@@ -2975,6 +3027,7 @@ func (cc *ClientConn) Ping(ctx context.Context) error {
func (rl *clientConnReadLoop) processPing(f *PingFrame) error {
if f.IsAck() {
cc := rl.cc
defer cc.maybeCallStateHook()
cc.mu.Lock()
defer cc.mu.Unlock()
// If ack, notify listener if any
@@ -3198,9 +3251,13 @@ func registerHTTPSProtocol(t *http.Transport, rt noDialH2RoundTripper) (err erro
}
// noDialH2RoundTripper is a RoundTripper which only tries to complete the request
// if there's already has a cached connection to the host.
// if there's already a cached connection to the host.
// (The field is exported so it can be accessed via reflect from net/http; tested
// by TestNoDialH2RoundTripperType)
//
// A noDialH2RoundTripper is registered with http1.Transport.RegisterProtocol,
// and the http1.Transport can use type assertions to call non-RoundTrip methods on it.
// This lets us expose, for example, NewClientConn to net/http.
type noDialH2RoundTripper struct{ *Transport }
func (rt noDialH2RoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
@@ -3211,6 +3268,85 @@ func (rt noDialH2RoundTripper) RoundTrip(req *http.Request) (*http.Response, err
return res, err
}
func (rt noDialH2RoundTripper) NewClientConn(conn net.Conn, internalStateHook func()) (http.RoundTripper, error) {
tr := rt.Transport
cc, err := tr.newClientConn(conn, tr.disableKeepAlives(), internalStateHook)
if err != nil {
return nil, err
}
// RoundTrip should block when the conn is at its concurrency limit,
// not return an error. Setting strictMaxConcurrentStreams enables this.
cc.strictMaxConcurrentStreams = true
return netHTTPClientConn{cc}, nil
}
// netHTTPClientConn wraps ClientConn and implements the interface net/http expects from
// the RoundTripper returned by NewClientConn.
type netHTTPClientConn struct {
cc *ClientConn
}
func (cc netHTTPClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
return cc.cc.RoundTrip(req)
}
func (cc netHTTPClientConn) Close() error {
return cc.cc.Close()
}
func (cc netHTTPClientConn) Err() error {
cc.cc.mu.Lock()
defer cc.cc.mu.Unlock()
if cc.cc.closed {
return errors.New("connection closed")
}
return nil
}
func (cc netHTTPClientConn) Reserve() error {
defer cc.cc.maybeCallStateHook()
cc.cc.mu.Lock()
defer cc.cc.mu.Unlock()
if !cc.cc.canReserveLocked() {
return errors.New("connection is unavailable")
}
cc.cc.streamsReserved++
return nil
}
func (cc netHTTPClientConn) Release() {
defer cc.cc.maybeCallStateHook()
cc.cc.mu.Lock()
defer cc.cc.mu.Unlock()
// We don't complain if streamsReserved is 0.
//
// This is consistent with RoundTrip: both Release and RoundTrip will
// consume a reservation iff one exists.
if cc.cc.streamsReserved > 0 {
cc.cc.streamsReserved--
}
}
func (cc netHTTPClientConn) Available() int {
cc.cc.mu.Lock()
defer cc.cc.mu.Unlock()
return cc.cc.availableLocked()
}
func (cc netHTTPClientConn) InFlight() int {
cc.cc.mu.Lock()
defer cc.cc.mu.Unlock()
return cc.cc.currentRequestCountLocked()
}
func (cc *ClientConn) maybeCallStateHook() {
if cc.internalStateHook != nil {
cc.internalStateHook()
}
}
func (t *Transport) idleConnTimeout() time.Duration {
// to keep things backwards compatible, we use non-zero values of
// IdleConnTimeout, followed by using the IdleConnTimeout on the underlying
+1 -1
View File
@@ -58,8 +58,8 @@ func RenderEvents(w http.ResponseWriter, req *http.Request, sensitive bool) {
Buckets: buckets,
}
data.Families = make([]string, 0, len(families))
famMu.RLock()
data.Families = make([]string, 0, len(families))
for name := range families {
data.Families = append(data.Families, name)
}
+122 -48
View File
@@ -64,6 +64,80 @@ func initOptions() {
func archInit() {
// From internal/cpu
const (
// eax bits
cpuid_AVXVNNI = 1 << 4
// ecx bits
cpuid_SSE3 = 1 << 0
cpuid_PCLMULQDQ = 1 << 1
cpuid_AVX512VBMI = 1 << 1
cpuid_AVX512VBMI2 = 1 << 6
cpuid_SSSE3 = 1 << 9
cpuid_AVX512GFNI = 1 << 8
cpuid_AVX512VAES = 1 << 9
cpuid_AVX512VNNI = 1 << 11
cpuid_AVX512BITALG = 1 << 12
cpuid_FMA = 1 << 12
cpuid_AVX512VPOPCNTDQ = 1 << 14
cpuid_SSE41 = 1 << 19
cpuid_SSE42 = 1 << 20
cpuid_POPCNT = 1 << 23
cpuid_AES = 1 << 25
cpuid_OSXSAVE = 1 << 27
cpuid_AVX = 1 << 28
// "Extended Feature Flag" bits returned in EBX for CPUID EAX=0x7 ECX=0x0
cpuid_BMI1 = 1 << 3
cpuid_AVX2 = 1 << 5
cpuid_BMI2 = 1 << 8
cpuid_ERMS = 1 << 9
cpuid_AVX512F = 1 << 16
cpuid_AVX512DQ = 1 << 17
cpuid_ADX = 1 << 19
cpuid_AVX512CD = 1 << 28
cpuid_SHA = 1 << 29
cpuid_AVX512BW = 1 << 30
cpuid_AVX512VL = 1 << 31
// "Extended Feature Flag" bits returned in ECX for CPUID EAX=0x7 ECX=0x0
cpuid_AVX512_VBMI = 1 << 1
cpuid_AVX512_VBMI2 = 1 << 6
cpuid_GFNI = 1 << 8
cpuid_AVX512VPCLMULQDQ = 1 << 10
cpuid_AVX512_BITALG = 1 << 12
// edx bits
cpuid_FSRM = 1 << 4
// edx bits for CPUID 0x80000001
cpuid_RDTSCP = 1 << 27
)
// Additional constants not in internal/cpu
const (
// eax=1: edx
cpuid_SSE2 = 1 << 26
// eax=1: ecx
cpuid_CX16 = 1 << 13
cpuid_RDRAND = 1 << 30
// eax=7,ecx=0: ebx
cpuid_RDSEED = 1 << 18
cpuid_AVX512IFMA = 1 << 21
cpuid_AVX512PF = 1 << 26
cpuid_AVX512ER = 1 << 27
// eax=7,ecx=0: edx
cpuid_AVX5124VNNIW = 1 << 2
cpuid_AVX5124FMAPS = 1 << 3
cpuid_AMXBF16 = 1 << 22
cpuid_AMXTile = 1 << 24
cpuid_AMXInt8 = 1 << 25
// eax=7,ecx=1: eax
cpuid_AVX512BF16 = 1 << 5
cpuid_AVXIFMA = 1 << 23
// eax=7,ecx=1: edx
cpuid_AVXVNNIInt8 = 1 << 4
)
Initialized = true
maxID, _, _, _ := cpuid(0, 0)
@@ -73,90 +147,90 @@ func archInit() {
}
_, _, ecx1, edx1 := cpuid(1, 0)
X86.HasSSE2 = isSet(26, edx1)
X86.HasSSE2 = isSet(edx1, cpuid_SSE2)
X86.HasSSE3 = isSet(0, ecx1)
X86.HasPCLMULQDQ = isSet(1, ecx1)
X86.HasSSSE3 = isSet(9, ecx1)
X86.HasFMA = isSet(12, ecx1)
X86.HasCX16 = isSet(13, ecx1)
X86.HasSSE41 = isSet(19, ecx1)
X86.HasSSE42 = isSet(20, ecx1)
X86.HasPOPCNT = isSet(23, ecx1)
X86.HasAES = isSet(25, ecx1)
X86.HasOSXSAVE = isSet(27, ecx1)
X86.HasRDRAND = isSet(30, ecx1)
X86.HasSSE3 = isSet(ecx1, cpuid_SSE3)
X86.HasPCLMULQDQ = isSet(ecx1, cpuid_PCLMULQDQ)
X86.HasSSSE3 = isSet(ecx1, cpuid_SSSE3)
X86.HasFMA = isSet(ecx1, cpuid_FMA)
X86.HasCX16 = isSet(ecx1, cpuid_CX16)
X86.HasSSE41 = isSet(ecx1, cpuid_SSE41)
X86.HasSSE42 = isSet(ecx1, cpuid_SSE42)
X86.HasPOPCNT = isSet(ecx1, cpuid_POPCNT)
X86.HasAES = isSet(ecx1, cpuid_AES)
X86.HasOSXSAVE = isSet(ecx1, cpuid_OSXSAVE)
X86.HasRDRAND = isSet(ecx1, cpuid_RDRAND)
var osSupportsAVX, osSupportsAVX512 bool
// For XGETBV, OSXSAVE bit is required and sufficient.
if X86.HasOSXSAVE {
eax, _ := xgetbv()
// Check if XMM and YMM registers have OS support.
osSupportsAVX = isSet(1, eax) && isSet(2, eax)
osSupportsAVX = isSet(eax, 1<<1) && isSet(eax, 1<<2)
if runtime.GOOS == "darwin" {
// Darwin requires special AVX512 checks, see cpu_darwin_x86.go
osSupportsAVX512 = osSupportsAVX && darwinSupportsAVX512()
} else {
// Check if OPMASK and ZMM registers have OS support.
osSupportsAVX512 = osSupportsAVX && isSet(5, eax) && isSet(6, eax) && isSet(7, eax)
osSupportsAVX512 = osSupportsAVX && isSet(eax, 1<<5) && isSet(eax, 1<<6) && isSet(eax, 1<<7)
}
}
X86.HasAVX = isSet(28, ecx1) && osSupportsAVX
X86.HasAVX = isSet(ecx1, cpuid_AVX) && osSupportsAVX
if maxID < 7 {
return
}
eax7, ebx7, ecx7, edx7 := cpuid(7, 0)
X86.HasBMI1 = isSet(3, ebx7)
X86.HasAVX2 = isSet(5, ebx7) && osSupportsAVX
X86.HasBMI2 = isSet(8, ebx7)
X86.HasERMS = isSet(9, ebx7)
X86.HasRDSEED = isSet(18, ebx7)
X86.HasADX = isSet(19, ebx7)
X86.HasBMI1 = isSet(ebx7, cpuid_BMI1)
X86.HasAVX2 = isSet(ebx7, cpuid_AVX2) && osSupportsAVX
X86.HasBMI2 = isSet(ebx7, cpuid_BMI2)
X86.HasERMS = isSet(ebx7, cpuid_ERMS)
X86.HasRDSEED = isSet(ebx7, cpuid_RDSEED)
X86.HasADX = isSet(ebx7, cpuid_ADX)
X86.HasAVX512 = isSet(16, ebx7) && osSupportsAVX512 // Because avx-512 foundation is the core required extension
X86.HasAVX512 = isSet(ebx7, cpuid_AVX512F) && osSupportsAVX512 // Because avx-512 foundation is the core required extension
if X86.HasAVX512 {
X86.HasAVX512F = true
X86.HasAVX512CD = isSet(28, ebx7)
X86.HasAVX512ER = isSet(27, ebx7)
X86.HasAVX512PF = isSet(26, ebx7)
X86.HasAVX512VL = isSet(31, ebx7)
X86.HasAVX512BW = isSet(30, ebx7)
X86.HasAVX512DQ = isSet(17, ebx7)
X86.HasAVX512IFMA = isSet(21, ebx7)
X86.HasAVX512VBMI = isSet(1, ecx7)
X86.HasAVX5124VNNIW = isSet(2, edx7)
X86.HasAVX5124FMAPS = isSet(3, edx7)
X86.HasAVX512VPOPCNTDQ = isSet(14, ecx7)
X86.HasAVX512VPCLMULQDQ = isSet(10, ecx7)
X86.HasAVX512VNNI = isSet(11, ecx7)
X86.HasAVX512GFNI = isSet(8, ecx7)
X86.HasAVX512VAES = isSet(9, ecx7)
X86.HasAVX512VBMI2 = isSet(6, ecx7)
X86.HasAVX512BITALG = isSet(12, ecx7)
X86.HasAVX512CD = isSet(ebx7, cpuid_AVX512CD)
X86.HasAVX512ER = isSet(ebx7, cpuid_AVX512ER)
X86.HasAVX512PF = isSet(ebx7, cpuid_AVX512PF)
X86.HasAVX512VL = isSet(ebx7, cpuid_AVX512VL)
X86.HasAVX512BW = isSet(ebx7, cpuid_AVX512BW)
X86.HasAVX512DQ = isSet(ebx7, cpuid_AVX512DQ)
X86.HasAVX512IFMA = isSet(ebx7, cpuid_AVX512IFMA)
X86.HasAVX512VBMI = isSet(ecx7, cpuid_AVX512_VBMI)
X86.HasAVX5124VNNIW = isSet(edx7, cpuid_AVX5124VNNIW)
X86.HasAVX5124FMAPS = isSet(edx7, cpuid_AVX5124FMAPS)
X86.HasAVX512VPOPCNTDQ = isSet(ecx7, cpuid_AVX512VPOPCNTDQ)
X86.HasAVX512VPCLMULQDQ = isSet(ecx7, cpuid_AVX512VPCLMULQDQ)
X86.HasAVX512VNNI = isSet(ecx7, cpuid_AVX512VNNI)
X86.HasAVX512GFNI = isSet(ecx7, cpuid_AVX512GFNI)
X86.HasAVX512VAES = isSet(ecx7, cpuid_AVX512VAES)
X86.HasAVX512VBMI2 = isSet(ecx7, cpuid_AVX512VBMI2)
X86.HasAVX512BITALG = isSet(ecx7, cpuid_AVX512BITALG)
}
X86.HasAMXTile = isSet(24, edx7)
X86.HasAMXInt8 = isSet(25, edx7)
X86.HasAMXBF16 = isSet(22, edx7)
X86.HasAMXTile = isSet(edx7, cpuid_AMXTile)
X86.HasAMXInt8 = isSet(edx7, cpuid_AMXInt8)
X86.HasAMXBF16 = isSet(edx7, cpuid_AMXBF16)
// These features depend on the second level of extended features.
if eax7 >= 1 {
eax71, _, _, edx71 := cpuid(7, 1)
if X86.HasAVX512 {
X86.HasAVX512BF16 = isSet(5, eax71)
X86.HasAVX512BF16 = isSet(eax71, cpuid_AVX512BF16)
}
if X86.HasAVX {
X86.HasAVXIFMA = isSet(23, eax71)
X86.HasAVXVNNI = isSet(4, eax71)
X86.HasAVXVNNIInt8 = isSet(4, edx71)
X86.HasAVXIFMA = isSet(eax71, cpuid_AVXIFMA)
X86.HasAVXVNNI = isSet(eax71, cpuid_AVXVNNI)
X86.HasAVXVNNIInt8 = isSet(edx71, cpuid_AVXVNNIInt8)
}
}
}
func isSet(bitpos uint, value uint32) bool {
return value&(1<<bitpos) != 0
func isSet(hwc uint32, value uint32) bool {
return hwc&value != 0
}
+25 -16
View File
@@ -1027,11 +1027,15 @@ func (ld *loader) refine(response *DriverResponse) ([]*Package, error) {
// Precondition: ld.Mode&(NeedSyntax|NeedTypes|NeedTypesInfo) != 0.
func (ld *loader) loadPackage(lpkg *loaderPackage) {
if lpkg.PkgPath == "unsafe" {
// Fill in the blanks to avoid surprises.
// To avoid surprises, fill in the blanks consistent
// with other packages. (For example, some analyzers
// assert that each needed types.Info map is non-nil
// even when there is no syntax that would cause them
// to consult the map.)
lpkg.Types = types.Unsafe
lpkg.Fset = ld.Fset
lpkg.Syntax = []*ast.File{}
lpkg.TypesInfo = new(types.Info)
lpkg.TypesInfo = ld.newTypesInfo()
lpkg.TypesSizes = ld.sizes
return
}
@@ -1180,20 +1184,7 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) {
return
}
// Populate TypesInfo only if needed, as it
// causes the type checker to work much harder.
if ld.Config.Mode&NeedTypesInfo != 0 {
lpkg.TypesInfo = &types.Info{
Types: make(map[ast.Expr]types.TypeAndValue),
Defs: make(map[*ast.Ident]types.Object),
Uses: make(map[*ast.Ident]types.Object),
Implicits: make(map[ast.Node]types.Object),
Instances: make(map[*ast.Ident]types.Instance),
Scopes: make(map[ast.Node]*types.Scope),
Selections: make(map[*ast.SelectorExpr]*types.Selection),
FileVersions: make(map[*ast.File]string),
}
}
lpkg.TypesInfo = ld.newTypesInfo()
lpkg.TypesSizes = ld.sizes
importer := importerFunc(func(path string) (*types.Package, error) {
@@ -1307,6 +1298,24 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) {
lpkg.IllTyped = illTyped
}
func (ld *loader) newTypesInfo() *types.Info {
// Populate TypesInfo only if needed, as it
// causes the type checker to work much harder.
if ld.Config.Mode&NeedTypesInfo == 0 {
return nil
}
return &types.Info{
Types: make(map[ast.Expr]types.TypeAndValue),
Defs: make(map[*ast.Ident]types.Object),
Uses: make(map[*ast.Ident]types.Object),
Implicits: make(map[ast.Node]types.Object),
Instances: make(map[*ast.Ident]types.Instance),
Scopes: make(map[ast.Node]*types.Scope),
Selections: make(map[*ast.SelectorExpr]*types.Selection),
FileVersions: make(map[*ast.File]string),
}
}
// An importFunc is an implementation of the single-method
// types.Importer interface based on a function value.
type importerFunc func(path string) (*types.Package, error)
+1
View File
@@ -12,6 +12,7 @@ import (
// Callee returns the named target of a function call, if any:
// a function, method, builtin, or variable.
// It returns nil for a T(x) conversion.
//
// Functions and methods may potentially have type parameters.
//
+6 -9
View File
@@ -8,7 +8,6 @@ import (
"context"
"sync/atomic"
"time"
"unsafe"
"golang.org/x/tools/internal/event/label"
)
@@ -17,23 +16,21 @@ import (
// It may return a modified context and event.
type Exporter func(context.Context, Event, label.Map) context.Context
var (
exporter unsafe.Pointer
)
var exporter atomic.Pointer[Exporter]
// SetExporter sets the global exporter function that handles all events.
// The exporter is called synchronously from the event call site, so it should
// return quickly so as not to hold up user code.
func SetExporter(e Exporter) {
p := unsafe.Pointer(&e)
if e == nil {
// &e is always valid, and so p is always valid, but for the early abort
// of ProcessEvent to be efficient it needs to make the nil check on the
// pointer without having to dereference it, so we make the nil function
// also a nil pointer
p = nil
exporter.Store(nil)
} else {
exporter.Store(&e)
}
atomic.StorePointer(&exporter, p)
}
// deliver is called to deliver an event to the supplied exporter.
@@ -48,7 +45,7 @@ func deliver(ctx context.Context, exporter Exporter, ev Event) context.Context {
// Export is called to deliver an event to the global exporter if set.
func Export(ctx context.Context, ev Event) context.Context {
// get the global exporter and abort early if there is not one
exporterPtr := (*Exporter)(atomic.LoadPointer(&exporter))
exporterPtr := exporter.Load()
if exporterPtr == nil {
return ctx
}
@@ -61,7 +58,7 @@ func Export(ctx context.Context, ev Event) context.Context {
// It will fill in the time.
func ExportPair(ctx context.Context, begin, end Event) (context.Context, func()) {
// get the global exporter and abort early if there is not one
exporterPtr := (*Exporter)(atomic.LoadPointer(&exporter))
exporterPtr := exporter.Load()
if exporterPtr == nil {
return ctx, func() {}
}
+3 -9
View File
@@ -7,7 +7,6 @@ package label
import (
"fmt"
"io"
"reflect"
"slices"
"unsafe"
)
@@ -103,11 +102,10 @@ type stringptr unsafe.Pointer
// This method is for implementing new key types, label creation should
// normally be done with the Of method of the key.
func OfString(k Key, v string) Label {
hdr := (*reflect.StringHeader)(unsafe.Pointer(&v))
return Label{
key: k,
packed: uint64(hdr.Len),
untyped: stringptr(hdr.Data),
packed: uint64(len(v)),
untyped: stringptr(unsafe.StringData(v)),
}
}
@@ -116,11 +114,7 @@ func OfString(k Key, v string) Label {
// This method is for implementing new key types, for type safety normal
// access should be done with the From method of the key.
func (t Label) UnpackString() string {
var v string
hdr := (*reflect.StringHeader)(unsafe.Pointer(&v))
hdr.Data = uintptr(t.untyped.(stringptr))
hdr.Len = int(t.packed)
return v
return unsafe.String((*byte)(t.untyped.(stringptr)), int(t.packed))
}
// Valid returns true if the Label is a valid one (it has a key).
+316 -310
View File
@@ -12,360 +12,364 @@ type pkginfo struct {
}
var deps = [...]pkginfo{
{"archive/tar", "\x03n\x03E<\x01\n\x01$\x01\x01\x02\x05\b\x02\x01\x02\x02\f"},
{"archive/zip", "\x02\x04d\a\x03\x12\x021<\x01+\x05\x01\x0f\x03\x02\x0e\x04"},
{"bufio", "\x03n\x84\x01D\x14"},
{"bytes", "q*Z\x03\fG\x02\x02"},
{"archive/tar", "\x03p\x03F=\x01\n\x01$\x01\x01\x02\x05\b\x02\x01\x02\x02\f"},
{"archive/zip", "\x02\x04f\a\x03\x13\x021=\x01+\x05\x01\x0f\x03\x02\x0e\x04"},
{"bufio", "\x03p\x86\x01D\x14"},
{"bytes", "s+[\x03\fG\x02\x02"},
{"cmp", ""},
{"compress/bzip2", "\x02\x02\xf1\x01A"},
{"compress/flate", "\x02o\x03\x81\x01\f\x033\x01\x03"},
{"compress/gzip", "\x02\x04d\a\x03\x14mT"},
{"compress/lzw", "\x02o\x03\x81\x01"},
{"compress/zlib", "\x02\x04d\a\x03\x12\x01n"},
{"container/heap", "\xb7\x02"},
{"compress/bzip2", "\x02\x02\xf5\x01A"},
{"compress/flate", "\x02q\x03\x83\x01\f\x033\x01\x03"},
{"compress/gzip", "\x02\x04f\a\x03\x15nT"},
{"compress/lzw", "\x02q\x03\x83\x01"},
{"compress/zlib", "\x02\x04f\a\x03\x13\x01o"},
{"container/heap", "\xbb\x02"},
{"container/list", ""},
{"container/ring", ""},
{"context", "q[o\x01\r"},
{"crypto", "\x86\x01oC"},
{"crypto/aes", "\x10\n\t\x95\x02"},
{"crypto/cipher", "\x03 \x01\x01\x1f\x11\x1c+Y"},
{"crypto/des", "\x10\x15\x1f-+\x9c\x01\x03"},
{"crypto/dsa", "D\x04)\x84\x01\r"},
{"crypto/ecdh", "\x03\v\f\x10\x04\x16\x04\r\x1c\x84\x01"},
{"crypto/ecdsa", "\x0e\x05\x03\x04\x01\x10\a\v\x06\x01\x04\f\x01\x1c\x84\x01\r\x05K\x01"},
{"crypto/ed25519", "\x0e\x1e\x11\a\n\a\x1c\x84\x01C"},
{"crypto/elliptic", "2?\x84\x01\r9"},
{"context", "s\\p\x01\r"},
{"crypto", "\x89\x01pC"},
{"crypto/aes", "\x10\n\t\x99\x02"},
{"crypto/cipher", "\x03 \x01\x01 \x12\x1c,Z"},
{"crypto/des", "\x10\x15 .,\x9d\x01\x03"},
{"crypto/dsa", "E\x04*\x86\x01\r"},
{"crypto/ecdh", "\x03\v\f\x10\x04\x17\x04\x0e\x1c\x86\x01"},
{"crypto/ecdsa", "\x0e\x05\x03\x04\x01\x10\b\v\x06\x01\x04\r\x01\x1c\x86\x01\r\x05K\x01"},
{"crypto/ed25519", "\x0e\x1e\x12\a\v\a\x1c\x86\x01C"},
{"crypto/elliptic", "3@\x86\x01\r9"},
{"crypto/fips140", "\"\x05"},
{"crypto/hkdf", "/\x14\x01-\x15"},
{"crypto/hmac", "\x1a\x16\x13\x01\x111"},
{"crypto/internal/boring", "\x0e\x02\ri"},
{"crypto/internal/boring/bbig", "\x1a\xe8\x01M"},
{"crypto/internal/boring/bcache", "\xbc\x02\x13"},
{"crypto/hkdf", "/\x15\x01.\x16"},
{"crypto/hmac", "\x1a\x16\x14\x01\x122"},
{"crypto/internal/boring", "\x0e\x02\rl"},
{"crypto/internal/boring/bbig", "\x1a\xec\x01M"},
{"crypto/internal/boring/bcache", "\xc0\x02\x13"},
{"crypto/internal/boring/sig", ""},
{"crypto/internal/constanttime", ""},
{"crypto/internal/cryptotest", "\x03\r\n\b%\x0e\x19\x06\x12\x12 \x04\x06\t\x18\x01\x11\x11\x1b\x01\a\x05\b\x03\x05\v"},
{"crypto/internal/entropy", "I"},
{"crypto/internal/entropy/v1.0.0", "B/\x93\x018\x13"},
{"crypto/internal/fips140", "A0\xbd\x01\v\x16"},
{"crypto/internal/fips140/aes", "\x03\x1f\x03\x02\x13\x05\x01\x01\x06*\x93\x014"},
{"crypto/internal/fips140/aes/gcm", "\"\x01\x02\x02\x02\x11\x05\x01\a*\x90\x01"},
{"crypto/internal/fips140/alias", "\xcf\x02"},
{"crypto/internal/fips140/bigmod", "'\x18\x01\a*\x93\x01"},
{"crypto/internal/fips140/check", "\"\x0e\x06\t\x02\xb4\x01Z"},
{"crypto/internal/fips140/check/checktest", "'\x87\x02!"},
{"crypto/internal/fips140/drbg", "\x03\x1e\x01\x01\x04\x13\x05\t\x01(\x84\x01\x0f7\x01"},
{"crypto/internal/fips140/ecdh", "\x03\x1f\x05\x02\t\r2\x84\x01\x0f7"},
{"crypto/internal/fips140/ecdsa", "\x03\x1f\x04\x01\x02\a\x02\x069\x15oF"},
{"crypto/internal/fips140/ed25519", "\x03\x1f\x05\x02\x04\v9\xc7\x01\x03"},
{"crypto/internal/fips140/edwards25519", "\x1e\t\a\x112\x93\x017"},
{"crypto/internal/fips140/edwards25519/field", "'\x13\x052\x93\x01"},
{"crypto/internal/fips140/hkdf", "\x03\x1f\x05\t\x06;\x15"},
{"crypto/internal/fips140/hmac", "\x03\x1f\x14\x01\x019\x15"},
{"crypto/internal/fips140/mlkem", "\x03\x1f\x05\x02\x0e\x03\x052\xca\x01"},
{"crypto/internal/fips140/nistec", "\x1e\t\f\f2\x93\x01*\r\x14"},
{"crypto/internal/fips140/nistec/fiat", "'\x137\x93\x01"},
{"crypto/internal/fips140/pbkdf2", "\x03\x1f\x05\t\x06;\x15"},
{"crypto/internal/fips140/rsa", "\x03\x1b\x04\x04\x01\x02\r\x01\x01\x027\x15oF"},
{"crypto/internal/fips140/sha256", "\x03\x1f\x1d\x01\a*\x15~"},
{"crypto/internal/fips140/sha3", "\x03\x1f\x18\x05\x011\x93\x01K"},
{"crypto/internal/fips140/sha512", "\x03\x1f\x1d\x01\a*\x15~"},
{"crypto/internal/fips140/ssh", "'_"},
{"crypto/internal/fips140/subtle", "\x1e\a\x1a\xc5\x01"},
{"crypto/internal/fips140/tls12", "\x03\x1f\x05\t\x06\x029\x15"},
{"crypto/internal/fips140/tls13", "\x03\x1f\x05\b\a\t2\x15"},
{"crypto/internal/fips140cache", "\xae\x02\r&"},
{"crypto/internal/cryptotest", "\x03\r\n\b&\x0f\x19\x06\x13\x12 \x04\x06\t\x19\x01\x11\x11\x1b\x01\a\x05\b\x03\x05\v"},
{"crypto/internal/entropy", "J"},
{"crypto/internal/entropy/v1.0.0", "C0\x95\x018\x13"},
{"crypto/internal/fips140", "B1\xbf\x01\v\x16"},
{"crypto/internal/fips140/aes", "\x03\x1f\x03\x02\x14\x05\x01\x01\x06+\x95\x014"},
{"crypto/internal/fips140/aes/gcm", "\"\x01\x02\x02\x02\x12\x05\x01\a+\x92\x01"},
{"crypto/internal/fips140/alias", "\xd3\x02"},
{"crypto/internal/fips140/bigmod", "'\x19\x01\a+\x95\x01"},
{"crypto/internal/fips140/check", "\"\x0e\a\t\x02\xb7\x01Z"},
{"crypto/internal/fips140/check/checktest", "'\x8b\x02!"},
{"crypto/internal/fips140/drbg", "\x03\x1e\x01\x01\x04\x14\x05\t\x01)\x86\x01\x0f7\x01"},
{"crypto/internal/fips140/ecdh", "\x03\x1f\x05\x02\n\r3\x86\x01\x0f7"},
{"crypto/internal/fips140/ecdsa", "\x03\x1f\x04\x01\x02\a\x03\x06:\x16pF"},
{"crypto/internal/fips140/ed25519", "\x03\x1f\x05\x02\x04\f:\xc9\x01\x03"},
{"crypto/internal/fips140/edwards25519", "\x1e\t\a\x123\x95\x017"},
{"crypto/internal/fips140/edwards25519/field", "'\x14\x053\x95\x01"},
{"crypto/internal/fips140/hkdf", "\x03\x1f\x05\t\a<\x16"},
{"crypto/internal/fips140/hmac", "\x03\x1f\x15\x01\x01:\x16"},
{"crypto/internal/fips140/mldsa", "\x03\x1b\x04\x05\x02\x0e\x01\x03\x053\x95\x017"},
{"crypto/internal/fips140/mlkem", "\x03\x1f\x05\x02\x0f\x03\x053\xcc\x01"},
{"crypto/internal/fips140/nistec", "\x1e\t\r\f3\x95\x01*\r\x14"},
{"crypto/internal/fips140/nistec/fiat", "'\x148\x95\x01"},
{"crypto/internal/fips140/pbkdf2", "\x03\x1f\x05\t\a<\x16"},
{"crypto/internal/fips140/rsa", "\x03\x1b\x04\x04\x01\x02\x0e\x01\x01\x028\x16pF"},
{"crypto/internal/fips140/sha256", "\x03\x1f\x1e\x01\a+\x16\x7f"},
{"crypto/internal/fips140/sha3", "\x03\x1f\x19\x05\x012\x95\x01K"},
{"crypto/internal/fips140/sha512", "\x03\x1f\x1e\x01\a+\x16\x7f"},
{"crypto/internal/fips140/ssh", "'b"},
{"crypto/internal/fips140/subtle", "\x1e\a\x1b\xc8\x01"},
{"crypto/internal/fips140/tls12", "\x03\x1f\x05\t\a\x02:\x16"},
{"crypto/internal/fips140/tls13", "\x03\x1f\x05\b\b\t3\x16"},
{"crypto/internal/fips140cache", "\xb2\x02\r&"},
{"crypto/internal/fips140deps", ""},
{"crypto/internal/fips140deps/byteorder", "\x9c\x01"},
{"crypto/internal/fips140deps/cpu", "\xb1\x01\a"},
{"crypto/internal/fips140deps/godebug", "\xb9\x01"},
{"crypto/internal/fips140deps/time", "\xc9\x02"},
{"crypto/internal/fips140hash", "7\x1c3\xc9\x01"},
{"crypto/internal/fips140only", ")\r\x01\x01N3<"},
{"crypto/internal/fips140deps/byteorder", "\x9f\x01"},
{"crypto/internal/fips140deps/cpu", "\xb4\x01\a"},
{"crypto/internal/fips140deps/godebug", "\xbc\x01"},
{"crypto/internal/fips140deps/time", "\xcd\x02"},
{"crypto/internal/fips140hash", "8\x1d4\xca\x01"},
{"crypto/internal/fips140only", ")\x0e\x01\x01P3="},
{"crypto/internal/fips140test", ""},
{"crypto/internal/hpke", "\x0e\x01\x01\x03\x056#+hM"},
{"crypto/internal/impl", "\xb9\x02"},
{"crypto/internal/randutil", "\xf5\x01\x12"},
{"crypto/internal/sysrand", "qo! \r\r\x01\x01\f\x06"},
{"crypto/internal/sysrand/internal/seccomp", "q"},
{"crypto/md5", "\x0e6-\x15\x16h"},
{"crypto/mlkem", "1"},
{"crypto/pbkdf2", "4\x0f\x01-\x15"},
{"crypto/rand", "\x1a\b\a\x1b\x04\x01(\x84\x01\rM"},
{"crypto/rc4", "%\x1f-\xc7\x01"},
{"crypto/rsa", "\x0e\f\x01\v\x0f\x0e\x01\x04\x06\a\x1c\x03\x123<\f\x01"},
{"crypto/sha1", "\x0e\f*\x03*\x15\x16\x15S"},
{"crypto/sha256", "\x0e\f\x1cP"},
{"crypto/sha3", "\x0e)O\xc9\x01"},
{"crypto/sha512", "\x0e\f\x1eN"},
{"crypto/subtle", "\x1e\x1c\x9c\x01X"},
{"crypto/tls", "\x03\b\x02\x01\x01\x01\x01\x02\x01\x01\x01\x02\x01\x01\t\x01\r\n\x01\n\x05\x03\x01\x01\x01\x01\x02\x01\x02\x01\x17\x02\x03\x12\x16\x15\b<\x16\x16\r\b\x01\x01\x01\x02\x01\r\x06\x02\x01\x0f"},
{"crypto/tls/internal/fips140tls", "\x17\xa5\x02"},
{"crypto/x509", "\x03\v\x01\x01\x01\x01\x01\x01\x01\x015\x05\x01\x01\x02\x05\x0e\x06\x02\x02\x03E\x039\x01\x02\b\x01\x01\x02\a\x10\x05\x01\x06\x02\x05\b\x02\x01\x02\x0e\x02\x01\x01\x02\x03\x01"},
{"crypto/x509/pkix", "g\x06\a\x8e\x01G"},
{"database/sql", "\x03\nN\x16\x03\x81\x01\v\a\"\x05\b\x02\x03\x01\r\x02\x02\x02"},
{"database/sql/driver", "\rd\x03\xb5\x01\x0f\x11"},
{"debug/buildinfo", "\x03[\x02\x01\x01\b\a\x03e\x1a\x02\x01+\x0f\x1f"},
{"debug/dwarf", "\x03g\a\x03\x81\x011\x11\x01\x01"},
{"debug/elf", "\x03\x06T\r\a\x03e\x1b\x01\f \x17\x01\x16"},
{"debug/gosym", "\x03g\n\xc3\x01\x01\x01\x02"},
{"debug/macho", "\x03\x06T\r\ne\x1c,\x17\x01"},
{"debug/pe", "\x03\x06T\r\a\x03e\x1c,\x17\x01\x16"},
{"debug/plan9obj", "j\a\x03e\x1c,"},
{"embed", "q*A\x19\x01S"},
{"crypto/internal/hpke", "\x03\v\x01\x01\x03\x055\x03\x04\x01\x01\x16\a\x03\x13\xcc\x01"},
{"crypto/internal/impl", "\xbd\x02"},
{"crypto/internal/randutil", "\xf9\x01\x12"},
{"crypto/internal/sysrand", "sq! \r\r\x01\x01\f\x06"},
{"crypto/internal/sysrand/internal/seccomp", "s"},
{"crypto/md5", "\x0e7.\x16\x16i"},
{"crypto/mlkem", "\x0e$"},
{"crypto/mlkem/mlkemtest", "2\x1b&"},
{"crypto/pbkdf2", "5\x0f\x01.\x16"},
{"crypto/rand", "\x1a\b\a\x1c\x04\x01)\x86\x01\rM"},
{"crypto/rc4", "% .\xc9\x01"},
{"crypto/rsa", "\x0e\f\x01\v\x10\x0e\x01\x04\a\a\x1c\x03\x133=\f\x01"},
{"crypto/sha1", "\x0e\f+\x03+\x16\x16\x15T"},
{"crypto/sha256", "\x0e\f\x1dR"},
{"crypto/sha3", "\x0e*Q\xca\x01"},
{"crypto/sha512", "\x0e\f\x1fP"},
{"crypto/subtle", "\x1e\x1d\x9f\x01X"},
{"crypto/tls", "\x03\b\x02\x01\x01\x01\x01\x02\x01\x01\x01\x02\x01\x01\t\x01\x0e\n\x01\n\x05\x04\x01\x01\x01\x01\x02\x01\x02\x01\x17\x02\x03\x13\x16\x15\b=\x16\x16\r\b\x01\x01\x01\x02\x01\r\x06\x02\x01\x0f"},
{"crypto/tls/internal/fips140tls", "\x17\xa9\x02"},
{"crypto/x509", "\x03\v\x01\x01\x01\x01\x01\x01\x01\x016\x06\x01\x01\x02\x05\x0e\x06\x02\x02\x03F\x03:\x01\x02\b\x01\x01\x02\a\x10\x05\x01\x06\a\b\x02\x01\x02\x0e\x02\x01\x01\x02\x03\x01"},
{"crypto/x509/pkix", "i\x06\a\x90\x01G"},
{"database/sql", "\x03\nP\x16\x03\x83\x01\v\a\"\x05\b\x02\x03\x01\r\x02\x02\x02"},
{"database/sql/driver", "\rf\x03\xb7\x01\x0f\x11"},
{"debug/buildinfo", "\x03]\x02\x01\x01\b\a\x03g\x1a\x02\x01+\x0f\x1f"},
{"debug/dwarf", "\x03i\a\x03\x83\x011\x11\x01\x01"},
{"debug/elf", "\x03\x06V\r\a\x03g\x1b\x01\f \x17\x01\x16"},
{"debug/gosym", "\x03i\n\xc5\x01\x01\x01\x02"},
{"debug/macho", "\x03\x06V\r\ng\x1c,\x17\x01"},
{"debug/pe", "\x03\x06V\r\a\x03g\x1c,\x17\x01\x16"},
{"debug/plan9obj", "l\a\x03g\x1c,"},
{"embed", "s+B\x19\x01S"},
{"embed/internal/embedtest", ""},
{"encoding", ""},
{"encoding/ascii85", "\xf5\x01C"},
{"encoding/asn1", "\x03n\x03e(\x01'\r\x02\x01\x10\x03\x01"},
{"encoding/base32", "\xf5\x01A\x02"},
{"encoding/base64", "\x9c\x01YA\x02"},
{"encoding/binary", "q\x84\x01\f(\r\x05"},
{"encoding/csv", "\x02\x01n\x03\x81\x01D\x12\x02"},
{"encoding/gob", "\x02c\x05\a\x03e\x1c\v\x01\x03\x1d\b\x12\x01\x0f\x02"},
{"encoding/hex", "q\x03\x81\x01A\x03"},
{"encoding/json", "\x03\x01a\x04\b\x03\x81\x01\f(\r\x02\x01\x02\x10\x01\x01\x02"},
{"encoding/pem", "\x03f\b\x84\x01A\x03"},
{"encoding/xml", "\x02\x01b\f\x03\x81\x014\x05\n\x01\x02\x10\x02"},
{"errors", "\xcc\x01\x83\x01"},
{"expvar", "nK@\b\v\x15\r\b\x02\x03\x01\x11"},
{"flag", "e\f\x03\x81\x01,\b\x05\b\x02\x01\x10"},
{"fmt", "qE&\x19\f \b\r\x02\x03\x12"},
{"go/ast", "\x03\x01p\x0e\x01r\x03)\b\r\x02\x01\x12\x02"},
{"go/build", "\x02\x01n\x03\x01\x02\x02\a\x02\x01\x17\x1f\x04\x02\b\x1b\x13\x01+\x01\x04\x01\a\b\x02\x01\x12\x02\x02"},
{"go/build/constraint", "q\xc7\x01\x01\x12\x02"},
{"go/constant", "t\x0f~\x01\x024\x01\x02\x12"},
{"go/doc", "\x04p\x01\x05\t=51\x10\x02\x01\x12\x02"},
{"go/doc/comment", "\x03q\xc2\x01\x01\x01\x01\x12\x02"},
{"go/format", "\x03q\x01\v\x01\x02rD"},
{"go/importer", "v\a\x01\x01\x04\x01q9"},
{"go/internal/gccgoimporter", "\x02\x01[\x13\x03\x04\v\x01o\x02,\x01\x05\x11\x01\f\b"},
{"go/internal/gcimporter", "\x02r\x0f\x010\x05\r/,\x15\x03\x02"},
{"go/internal/srcimporter", "t\x01\x01\n\x03\x01q,\x01\x05\x12\x02\x14"},
{"go/parser", "\x03n\x03\x01\x02\v\x01r\x01+\x06\x12"},
{"go/printer", "t\x01\x02\x03\tr\f \x15\x02\x01\x02\v\x05\x02"},
{"go/scanner", "\x03q\x0fr2\x10\x01\x13\x02"},
{"go/token", "\x04p\x84\x01>\x02\x03\x01\x0f\x02"},
{"go/types", "\x03\x01\x06g\x03\x01\x03\b\x03\x024\x062\x04\x03\t \x06\a\b\x01\x01\x01\x02\x01\x0f\x02\x02"},
{"go/version", "\xbe\x01{"},
{"hash", "\xf5\x01"},
{"hash/adler32", "q\x15\x16"},
{"hash/crc32", "q\x15\x16\x15\x8a\x01\x01\x13"},
{"hash/crc64", "q\x15\x16\x9f\x01"},
{"hash/fnv", "q\x15\x16h"},
{"hash/maphash", "\x86\x01\x11<|"},
{"html", "\xb9\x02\x02\x12"},
{"html/template", "\x03k\x06\x18-<\x01\n!\x05\x01\x02\x03\f\x01\x02\f\x01\x03\x02"},
{"image", "\x02o\x1ef\x0f4\x03\x01"},
{"encoding/ascii85", "\xf9\x01C"},
{"encoding/asn1", "\x03p\x03g(\x01'\r\x02\x01\x10\x03\x01"},
{"encoding/base32", "\xf9\x01A\x02"},
{"encoding/base64", "\x9f\x01ZA\x02"},
{"encoding/binary", "s\x86\x01\f(\r\x05"},
{"encoding/csv", "\x02\x01p\x03\x83\x01D\x12\x02"},
{"encoding/gob", "\x02e\x05\a\x03g\x1c\v\x01\x03\x1d\b\x12\x01\x0f\x02"},
{"encoding/hex", "s\x03\x83\x01A\x03"},
{"encoding/json", "\x03\x01c\x04\b\x03\x83\x01\f(\r\x02\x01\x02\x10\x01\x01\x02"},
{"encoding/pem", "\x03h\b\x86\x01A\x03"},
{"encoding/xml", "\x02\x01d\f\x03\x83\x014\x05\n\x01\x02\x10\x02"},
{"errors", "\xcf\x01\x84\x01"},
{"expvar", "pLA\b\v\x15\r\b\x02\x03\x01\x11"},
{"flag", "g\f\x03\x83\x01,\b\x05\b\x02\x01\x10"},
{"fmt", "sF'\x19\f \b\r\x02\x03\x12"},
{"go/ast", "\x03\x01r\x0f\x01s\x03)\b\r\x02\x01\x12\x02"},
{"go/build", "\x02\x01p\x03\x01\x02\x02\b\x02\x01\x17\x1f\x04\x02\b\x1c\x13\x01+\x01\x04\x01\a\b\x02\x01\x12\x02\x02"},
{"go/build/constraint", "s\xc9\x01\x01\x12\x02"},
{"go/constant", "v\x10\x7f\x01\x024\x01\x02\x12"},
{"go/doc", "\x04r\x01\x05\n=61\x10\x02\x01\x12\x02"},
{"go/doc/comment", "\x03s\xc4\x01\x01\x01\x01\x12\x02"},
{"go/format", "\x03s\x01\f\x01\x02sD"},
{"go/importer", "x\a\x01\x02\x04\x01r9"},
{"go/internal/gccgoimporter", "\x02\x01]\x13\x03\x04\f\x01p\x02,\x01\x05\x11\x01\f\b"},
{"go/internal/gcimporter", "\x02t\x10\x010\x05\r0,\x15\x03\x02"},
{"go/internal/scannerhooks", "\x86\x01"},
{"go/internal/srcimporter", "v\x01\x01\v\x03\x01r,\x01\x05\x12\x02\x14"},
{"go/parser", "\x03p\x03\x01\x02\b\x04\x01s\x01+\x06\x12"},
{"go/printer", "v\x01\x02\x03\ns\f \x15\x02\x01\x02\v\x05\x02"},
{"go/scanner", "\x03s\v\x05s2\x10\x01\x13\x02"},
{"go/token", "\x04r\x86\x01>\x02\x03\x01\x0f\x02"},
{"go/types", "\x03\x01\x06i\x03\x01\x03\t\x03\x024\x063\x04\x03\t \x06\a\b\x01\x01\x01\x02\x01\x0f\x02\x02"},
{"go/version", "\xc1\x01|"},
{"hash", "\xf9\x01"},
{"hash/adler32", "s\x16\x16"},
{"hash/crc32", "s\x16\x16\x15\x8b\x01\x01\x13"},
{"hash/crc64", "s\x16\x16\xa0\x01"},
{"hash/fnv", "s\x16\x16i"},
{"hash/maphash", "\x89\x01\x11<}"},
{"html", "\xbd\x02\x02\x12"},
{"html/template", "\x03m\x06\x19-=\x01\n!\x05\x01\x02\x03\f\x01\x02\f\x01\x03\x02"},
{"image", "\x02q\x1fg\x0f4\x03\x01"},
{"image/color", ""},
{"image/color/palette", "\x8f\x01"},
{"image/draw", "\x8e\x01\x01\x04"},
{"image/gif", "\x02\x01\x05i\x03\x1a\x01\x01\x01\vY"},
{"image/internal/imageutil", "\x8e\x01"},
{"image/jpeg", "\x02o\x1d\x01\x04b"},
{"image/png", "\x02\aa\n\x12\x02\x06\x01fC"},
{"index/suffixarray", "\x03g\a\x84\x01\f+\n\x01"},
{"internal/abi", "\xb8\x01\x97\x01"},
{"internal/asan", "\xcf\x02"},
{"internal/bisect", "\xae\x02\r\x01"},
{"internal/buildcfg", "tGf\x06\x02\x05\n\x01"},
{"internal/bytealg", "\xb1\x01\x9e\x01"},
{"image/color/palette", "\x92\x01"},
{"image/draw", "\x91\x01\x01\x04"},
{"image/gif", "\x02\x01\x05k\x03\x1b\x01\x01\x01\vZ\x0f"},
{"image/internal/imageutil", "\x91\x01"},
{"image/jpeg", "\x02q\x1e\x01\x04c"},
{"image/png", "\x02\ac\n\x13\x02\x06\x01gC"},
{"index/suffixarray", "\x03i\a\x86\x01\f+\n\x01"},
{"internal/abi", "\xbb\x01\x98\x01"},
{"internal/asan", "\xd3\x02"},
{"internal/bisect", "\xb2\x02\r\x01"},
{"internal/buildcfg", "vHg\x06\x02\x05\n\x01"},
{"internal/bytealg", "\xb4\x01\x9f\x01"},
{"internal/byteorder", ""},
{"internal/cfg", ""},
{"internal/cgrouptest", "tZS\x06\x0f\x02\x01\x04\x01"},
{"internal/chacha8rand", "\x9c\x01\x15\a\x97\x01"},
{"internal/cgrouptest", "v[T\x06\x0f\x02\x01\x04\x01"},
{"internal/chacha8rand", "\x9f\x01\x15\a\x98\x01"},
{"internal/copyright", ""},
{"internal/coverage", ""},
{"internal/coverage/calloc", ""},
{"internal/coverage/cfile", "n\x06\x16\x17\x01\x02\x01\x01\x01\x01\x01\x01\x01\"\x02&,\x06\a\n\x01\x03\r\x06"},
{"internal/coverage/cformat", "\x04p-\x04P\v6\x01\x02\r"},
{"internal/coverage/cmerge", "t-`"},
{"internal/coverage/decodecounter", "j\n-\v\x02G,\x17\x17"},
{"internal/coverage/decodemeta", "\x02h\n\x16\x17\v\x02G,"},
{"internal/coverage/encodecounter", "\x02h\n-\f\x01\x02E\v!\x15"},
{"internal/coverage/encodemeta", "\x02\x01g\n\x12\x04\x17\r\x02E,."},
{"internal/coverage/pods", "\x04p-\x80\x01\x06\x05\n\x02\x01"},
{"internal/coverage/rtcov", "\xcf\x02"},
{"internal/coverage/slicereader", "j\n\x81\x01Z"},
{"internal/coverage/slicewriter", "t\x81\x01"},
{"internal/coverage/stringtab", "t8\x04E"},
{"internal/coverage/cfile", "p\x06\x17\x17\x01\x02\x01\x01\x01\x01\x01\x01\x01\"\x02',\x06\a\n\x01\x03\r\x06"},
{"internal/coverage/cformat", "\x04r.\x04Q\v6\x01\x02\r"},
{"internal/coverage/cmerge", "v.a"},
{"internal/coverage/decodecounter", "l\n.\v\x02H,\x17\x17"},
{"internal/coverage/decodemeta", "\x02j\n\x17\x17\v\x02H,"},
{"internal/coverage/encodecounter", "\x02j\n.\f\x01\x02F\v!\x15"},
{"internal/coverage/encodemeta", "\x02\x01i\n\x13\x04\x17\r\x02F,."},
{"internal/coverage/pods", "\x04r.\x81\x01\x06\x05\n\x02\x01"},
{"internal/coverage/rtcov", "\xd3\x02"},
{"internal/coverage/slicereader", "l\n\x83\x01Z"},
{"internal/coverage/slicewriter", "v\x83\x01"},
{"internal/coverage/stringtab", "v9\x04F"},
{"internal/coverage/test", ""},
{"internal/coverage/uleb128", ""},
{"internal/cpu", "\xcf\x02"},
{"internal/dag", "\x04p\xc2\x01\x03"},
{"internal/diff", "\x03q\xc3\x01\x02"},
{"internal/exportdata", "\x02\x01n\x03\x02c\x1c,\x01\x05\x11\x01\x02"},
{"internal/filepathlite", "q*A\x1a@"},
{"internal/fmtsort", "\x04\xa5\x02\r"},
{"internal/fuzz", "\x03\nE\x18\x04\x03\x03\x01\v\x036<\f\x03\x1d\x01\x05\x02\x05\n\x01\x02\x01\x01\f\x04\x02"},
{"internal/cpu", "\xd3\x02"},
{"internal/dag", "\x04r\xc4\x01\x03"},
{"internal/diff", "\x03s\xc5\x01\x02"},
{"internal/exportdata", "\x02\x01p\x03\x02e\x1c,\x01\x05\x11\x01\x02"},
{"internal/filepathlite", "s+B\x1a@"},
{"internal/fmtsort", "\x04\xa9\x02\r"},
{"internal/fuzz", "\x03\nG\x18\x04\x03\x03\x01\f\x036=\f\x03\x1d\x01\x05\x02\x05\n\x01\x02\x01\x01\f\x04\x02"},
{"internal/goarch", ""},
{"internal/godebug", "\x99\x01!\x81\x01\x01\x13"},
{"internal/godebug", "\x9c\x01!\x82\x01\x01\x13"},
{"internal/godebugs", ""},
{"internal/goexperiment", ""},
{"internal/goos", ""},
{"internal/goroot", "\xa1\x02\x01\x05\x12\x02"},
{"internal/goroot", "\xa5\x02\x01\x05\x12\x02"},
{"internal/gover", "\x04"},
{"internal/goversion", ""},
{"internal/lazyregexp", "\xa1\x02\v\r\x02"},
{"internal/lazytemplate", "\xf5\x01,\x18\x02\f"},
{"internal/msan", "\xcf\x02"},
{"internal/lazyregexp", "\xa5\x02\v\r\x02"},
{"internal/lazytemplate", "\xf9\x01,\x18\x02\f"},
{"internal/msan", "\xd3\x02"},
{"internal/nettrace", ""},
{"internal/obscuretestdata", "i\x8c\x01,"},
{"internal/oserror", "q"},
{"internal/pkgbits", "\x03O\x18\a\x03\x04\vr\r\x1f\r\n\x01"},
{"internal/obscuretestdata", "k\x8e\x01,"},
{"internal/oserror", "s"},
{"internal/pkgbits", "\x03Q\x18\a\x03\x04\fs\r\x1f\r\n\x01"},
{"internal/platform", ""},
{"internal/poll", "qj\x05\x159\r\x01\x01\f\x06"},
{"internal/profile", "\x03\x04j\x03\x81\x017\n\x01\x01\x01\x10"},
{"internal/poll", "sl\x05\x159\r\x01\x01\f\x06"},
{"internal/profile", "\x03\x04l\x03\x83\x017\n\x01\x01\x01\x10"},
{"internal/profilerecord", ""},
{"internal/race", "\x97\x01\xb8\x01"},
{"internal/reflectlite", "\x97\x01!:<!"},
{"internal/runtime/atomic", "\xb8\x01\x97\x01"},
{"internal/runtime/cgroup", "\x9b\x01<\x04t"},
{"internal/runtime/exithook", "\xcd\x01\x82\x01"},
{"internal/runtime/gc", "\xb8\x01"},
{"internal/runtime/gc/internal/gen", "\n`\n\x17j\x04\v\x1d\b\x10\x02"},
{"internal/runtime/gc/scan", "\xb1\x01\a\x18\x06y"},
{"internal/runtime/maps", "\x97\x01\x01 \n\t\t\x02y"},
{"internal/runtime/math", "\xb8\x01"},
{"internal/race", "\x9a\x01\xb9\x01"},
{"internal/reflectlite", "\x9a\x01!;<!"},
{"internal/runtime/atomic", "\xbb\x01\x98\x01"},
{"internal/runtime/cgroup", "\x9e\x01=\x04t"},
{"internal/runtime/exithook", "\xd0\x01\x83\x01"},
{"internal/runtime/gc", "\xbb\x01"},
{"internal/runtime/gc/internal/gen", "\nb\n\x18k\x04\v\x1d\b\x10\x02"},
{"internal/runtime/gc/scan", "\xb4\x01\a\x18\ay"},
{"internal/runtime/maps", "\x9a\x01\x01 \n\t\t\x03y"},
{"internal/runtime/math", "\xbb\x01"},
{"internal/runtime/pprof/label", ""},
{"internal/runtime/startlinetest", ""},
{"internal/runtime/sys", "\xb8\x01\x04"},
{"internal/runtime/syscall/linux", "\xb8\x01\x97\x01"},
{"internal/runtime/sys", "\xbb\x01\x04"},
{"internal/runtime/syscall/linux", "\xbb\x01\x98\x01"},
{"internal/runtime/wasitest", ""},
{"internal/saferio", "\xf5\x01Z"},
{"internal/singleflight", "\xbb\x02"},
{"internal/strconv", "\x84\x02K"},
{"internal/stringslite", "\x9b\x01\xb4\x01"},
{"internal/sync", "\x97\x01!\x13q\x13"},
{"internal/synctest", "\x97\x01\xb8\x01"},
{"internal/syscall/execenv", "\xbd\x02"},
{"internal/syscall/unix", "\xae\x02\x0e\x01\x12"},
{"internal/sysinfo", "\x02\x01\xae\x01D,\x18\x02"},
{"internal/saferio", "\xf9\x01Z"},
{"internal/singleflight", "\xbf\x02"},
{"internal/strconv", "\x88\x02K"},
{"internal/stringslite", "\x9e\x01\xb5\x01"},
{"internal/sync", "\x9a\x01!\x13r\x13"},
{"internal/synctest", "\x9a\x01\xb9\x01"},
{"internal/syscall/execenv", "\xc1\x02"},
{"internal/syscall/unix", "\xb2\x02\x0e\x01\x12"},
{"internal/sysinfo", "\x02\x01\xb1\x01E,\x18\x02"},
{"internal/syslist", ""},
{"internal/testenv", "\x03\nd\x02\x01)\x1b\x0f/+\x01\x05\a\n\x01\x02\x02\x01\v"},
{"internal/testhash", "\x03\x83\x01o\x118\v"},
{"internal/testlog", "\xbb\x02\x01\x13"},
{"internal/testpty", "q\x03\xad\x01"},
{"internal/trace", "\x02\x01\x01\x06`\a\x03u\x03\x03\x06\x03\t5\x01\x01\x01\x10\x06"},
{"internal/trace/internal/testgen", "\x03g\ns\x03\x02\x03\x011\v\r\x10"},
{"internal/trace/internal/tracev1", "\x03\x01f\a\x03{\x06\f5\x01"},
{"internal/trace/raw", "\x02h\nx\x03\x06C\x01\x12"},
{"internal/trace/testtrace", "\x02\x01n\x03o\x04\x03\x05\x01\x05,\v\x02\b\x02\x01\x05"},
{"internal/testenv", "\x03\nf\x02\x01*\x1b\x0f0+\x01\x05\a\n\x01\x02\x02\x01\v"},
{"internal/testhash", "\x03\x86\x01p\x118\v"},
{"internal/testlog", "\xbf\x02\x01\x13"},
{"internal/testpty", "s\x03\xaf\x01"},
{"internal/trace", "\x02\x01\x01\x06b\a\x03w\x03\x03\x06\x03\t5\x01\x01\x01\x10\x06"},
{"internal/trace/internal/testgen", "\x03i\nu\x03\x02\x03\x011\v\r\x10"},
{"internal/trace/internal/tracev1", "\x03\x01h\a\x03}\x06\f5\x01"},
{"internal/trace/raw", "\x02j\nz\x03\x06C\x01\x12"},
{"internal/trace/testtrace", "\x02\x01p\x03q\x04\x03\x05\x01\x05,\v\x02\b\x02\x01\x05"},
{"internal/trace/tracev2", ""},
{"internal/trace/traceviewer", "\x02a\v\x06\x19<\x1e\a\a\x04\b\v\x15\x01\x05\a\n\x01\x02\x0e"},
{"internal/trace/traceviewer", "\x02c\v\x06\x1a<\x1f\a\a\x04\b\v\x15\x01\x05\a\n\x01\x02\x0e"},
{"internal/trace/traceviewer/format", ""},
{"internal/trace/version", "tx\t"},
{"internal/txtar", "\x03q\xad\x01\x18"},
{"internal/types/errors", "\xb8\x02"},
{"internal/unsafeheader", "\xcf\x02"},
{"internal/xcoff", "]\r\a\x03e\x1c,\x17\x01"},
{"internal/zstd", "j\a\x03\x81\x01\x0f"},
{"io", "q\xca\x01"},
{"io/fs", "q**01\x10\x13\x04"},
{"io/ioutil", "\xf5\x01\x01+\x15\x03"},
{"iter", "\xcb\x01c!"},
{"log", "t\x81\x01\x05'\r\r\x01\r"},
{"internal/trace/version", "vz\t"},
{"internal/txtar", "\x03s\xaf\x01\x18"},
{"internal/types/errors", "\xbc\x02"},
{"internal/unsafeheader", "\xd3\x02"},
{"internal/xcoff", "_\r\a\x03g\x1c,\x17\x01"},
{"internal/zstd", "l\a\x03\x83\x01\x0f"},
{"io", "s\xcc\x01"},
{"io/fs", "s+*11\x10\x13\x04"},
{"io/ioutil", "\xf9\x01\x01+\x15\x03"},
{"iter", "\xce\x01d!"},
{"log", "v\x83\x01\x05'\r\r\x01\r"},
{"log/internal", ""},
{"log/slog", "\x03\nX\t\x03\x03\x81\x01\x04\x01\x02\x02\x03(\x05\b\x02\x01\x02\x01\r\x02\x02\x02"},
{"log/slog", "\x03\nZ\t\x03\x03\x83\x01\x04\x01\x02\x02\x03(\x05\b\x02\x01\x02\x01\r\x02\x02\x02"},
{"log/slog/internal", ""},
{"log/slog/internal/benchmarks", "\rd\x03\x81\x01\x06\x03:\x11"},
{"log/slog/internal/buffer", "\xbb\x02"},
{"log/syslog", "q\x03\x85\x01\x12\x16\x18\x02\x0e"},
{"maps", "\xf8\x01W"},
{"math", "\xb1\x01SK"},
{"math/big", "\x03n\x03(\x15D\f\x03\x020\x02\x01\x02\x14"},
{"math/big/internal/asmgen", "\x03\x01p\x90\x012\x03"},
{"math/bits", "\xcf\x02"},
{"math/cmplx", "\x81\x02\x03"},
{"math/rand", "\xb9\x01H:\x01\x13"},
{"math/rand/v2", "q+\x03b\x03K"},
{"mime", "\x02\x01f\b\x03\x81\x01\v!\x15\x03\x02\x10\x02"},
{"mime/multipart", "\x02\x01K#\x03E<\v\x01\a\x02\x15\x02\x06\x0f\x02\x01\x16"},
{"mime/quotedprintable", "\x02\x01q\x81\x01"},
{"net", "\x04\td*\x1e\n\x05\x12\x01\x01\x04\x15\x01%\x06\r\b\x05\x01\x01\f\x06\a"},
{"net/http", "\x02\x01\x03\x01\x04\x02A\b\x13\x01\a\x03E<\x01\x03\a\x01\x03\x02\x02\x01\x02\x06\x02\x01\x01\n\x01\x01\x05\x01\x02\x05\b\x01\x01\x01\x02\x01\r\x02\x02\x02\b\x01\x01\x01"},
{"net/http/cgi", "\x02T\x1b\x03\x81\x01\x04\a\v\x01\x13\x01\x01\x01\x04\x01\x05\x02\b\x02\x01\x10\x0e"},
{"net/http/cookiejar", "\x04m\x03\x97\x01\x01\b\f\x16\x03\x02\x0e\x04"},
{"net/http/fcgi", "\x02\x01\n]\a\x03\x81\x01\x16\x01\x01\x14\x18\x02\x0e"},
{"net/http/httptest", "\x02\x01\nI\x02\x1b\x01\x81\x01\x04\x12\x01\n\t\x02\x17\x01\x02\x0e\x0e"},
{"net/http/httptrace", "\rImH\x14\n "},
{"net/http/httputil", "\x02\x01\nd\x03\x81\x01\x04\x0f\x03\x01\x05\x02\x01\v\x01\x19\x02\x0e\x0e"},
{"net/http/internal", "\x02\x01n\x03\x81\x01"},
{"net/http/internal/ascii", "\xb9\x02\x12"},
{"net/http/internal/httpcommon", "\rd\x03\x9d\x01\x0e\x01\x17\x01\x01\x02\x1c\x02"},
{"net/http/internal/testcert", "\xb9\x02"},
{"net/http/pprof", "\x02\x01\ng\x18-\x02\x0e,\x04\x13\x14\x01\r\x04\x03\x01\x02\x01\x10"},
{"log/slog/internal/benchmarks", "\rf\x03\x83\x01\x06\x03:\x11"},
{"log/slog/internal/buffer", "\xbf\x02"},
{"log/syslog", "s\x03\x87\x01\x12\x16\x18\x02\x0e"},
{"maps", "\xfc\x01W"},
{"math", "\xb4\x01TK"},
{"math/big", "\x03p\x03)\x15E\f\x03\x020\x02\x01\x02\x14"},
{"math/big/internal/asmgen", "\x03\x01r\x92\x012\x03"},
{"math/bits", "\xd3\x02"},
{"math/cmplx", "\x85\x02\x03"},
{"math/rand", "\xbc\x01I:\x01\x13"},
{"math/rand/v2", "s,\x03c\x03K"},
{"mime", "\x02\x01h\b\x03\x83\x01\v!\x15\x03\x02\x10\x02"},
{"mime/multipart", "\x02\x01M#\x03F=\v\x01\a\x02\x15\x02\x06\x0f\x02\x01\x16"},
{"mime/quotedprintable", "\x02\x01s\x83\x01"},
{"net", "\x04\tf+\x1e\n\x05\x13\x01\x01\x04\x15\x01%\x06\r\b\x05\x01\x01\f\x06\a"},
{"net/http", "\x02\x01\x03\x01\x04\x02C\b\x13\x01\a\x03F=\x01\x03\a\x01\x03\x02\x02\x01\x02\x06\x02\x01\x01\n\x01\x01\x05\x01\x02\x05\b\x01\x01\x01\x02\x01\r\x02\x02\x02\b\x01\x01\x01"},
{"net/http/cgi", "\x02V\x1b\x03\x83\x01\x04\a\v\x01\x13\x01\x01\x01\x04\x01\x05\x02\b\x02\x01\x10\x0e"},
{"net/http/cookiejar", "\x04o\x03\x99\x01\x01\b\a\x05\x16\x03\x02\x0e\x04"},
{"net/http/fcgi", "\x02\x01\n_\a\x03\x83\x01\x16\x01\x01\x14\x18\x02\x0e"},
{"net/http/httptest", "\x02\x01\nK\x02\x1b\x01\x83\x01\x04\x12\x01\n\t\x02\x17\x01\x02\x0e\x0e"},
{"net/http/httptrace", "\rKnI\x14\n "},
{"net/http/httputil", "\x02\x01\nf\x03\x83\x01\x04\x0f\x03\x01\x05\x02\x01\v\x01\x19\x02\x01\r\x0e"},
{"net/http/internal", "\x02\x01p\x03\x83\x01"},
{"net/http/internal/ascii", "\xbd\x02\x12"},
{"net/http/internal/httpcommon", "\rf\x03\x9f\x01\x0e\x01\x17\x01\x01\x02\x1c\x02"},
{"net/http/internal/testcert", "\xbd\x02"},
{"net/http/pprof", "\x02\x01\ni\x19-\x02\x0e-\x04\x13\x14\x01\r\x04\x03\x01\x02\x01\x10"},
{"net/internal/cgotest", ""},
{"net/internal/socktest", "t\xc7\x01\x02"},
{"net/mail", "\x02o\x03\x81\x01\x04\x0f\x03\x14\x1a\x02\x0e\x04"},
{"net/netip", "\x04m*\x01e\x034\x16"},
{"net/rpc", "\x02j\x05\x03\x0f\nh\x04\x12\x01\x1d\r\x03\x02"},
{"net/rpc/jsonrpc", "n\x03\x03\x81\x01\x16\x11\x1f"},
{"net/smtp", "\x192\v\x13\b\x03\x81\x01\x16\x14\x1a"},
{"net/textproto", "\x02\x01n\x03\x81\x01\f\n-\x01\x02\x14"},
{"net/url", "q\x03\xa7\x01\v\x10\x02\x01\x16"},
{"os", "q*\x01\x19\x03\x10\x13\x01\x03\x01\x05\x10\x018\b\x05\x01\x01\f\x06"},
{"os/exec", "\x03\ndH&\x01\x15\x01+\x06\a\n\x01\x04\f"},
{"os/exec/internal/fdtest", "\xbd\x02"},
{"os/signal", "\r\x94\x02\x15\x05\x02"},
{"os/user", "\x02\x01n\x03\x81\x01,\r\n\x01\x02"},
{"path", "q*\xb2\x01"},
{"path/filepath", "q*\x1aA+\r\b\x03\x04\x10"},
{"plugin", "q"},
{"reflect", "q&\x04\x1d\x13\b\x03\x05\x17\x06\t-\n\x03\x10\x02\x02"},
{"net/internal/socktest", "v\xc9\x01\x02"},
{"net/mail", "\x02q\x03\x83\x01\x04\x0f\x03\x14\x1a\x02\x0e\x04"},
{"net/netip", "\x04o+\x01f\x034\x16"},
{"net/rpc", "\x02l\x05\x03\x10\ni\x04\x12\x01\x1d\r\x03\x02"},
{"net/rpc/jsonrpc", "p\x03\x03\x83\x01\x16\x11\x1f"},
{"net/smtp", "\x193\f\x13\b\x03\x83\x01\x16\x14\x1a"},
{"net/textproto", "\x02\x01p\x03\x83\x01\f\n-\x01\x02\x14"},
{"net/url", "s\x03Fc\v\x10\x02\x01\x16"},
{"os", "s+\x01\x19\x03\x10\x14\x01\x03\x01\x05\x10\x018\b\x05\x01\x01\f\x06"},
{"os/exec", "\x03\nfI'\x01\x15\x01+\x06\a\n\x01\x04\f"},
{"os/exec/internal/fdtest", "\xc1\x02"},
{"os/signal", "\r\x98\x02\x15\x05\x02"},
{"os/user", "\x02\x01p\x03\x83\x01,\r\n\x01\x02"},
{"path", "s+\xb3\x01"},
{"path/filepath", "s+\x1aB+\r\b\x03\x04\x10"},
{"plugin", "s"},
{"reflect", "s'\x04\x1d\x13\b\x04\x05\x17\x06\t-\n\x03\x10\x02\x02"},
{"reflect/internal/example1", ""},
{"reflect/internal/example2", ""},
{"regexp", "\x03\xf2\x018\t\x02\x01\x02\x10\x02"},
{"regexp/syntax", "\xb6\x02\x01\x01\x01\x02\x10\x02"},
{"runtime", "\x97\x01\x04\x01\x03\f\x06\a\x02\x01\x01\x0e\x03\x01\x01\x01\x02\x01\x01\x02\x01\x04\x01\x10c"},
{"runtime/coverage", "\xa3\x01R"},
{"runtime/debug", "tTY\r\b\x02\x01\x10\x06"},
{"runtime/metrics", "\xba\x01G-!"},
{"runtime/pprof", "\x02\x01\x01\x03\x06]\a\x03#$\x0f+\f \r\b\x01\x01\x01\x02\x02\t\x03\x06"},
{"runtime/race", "\xb4\x02"},
{"regexp", "\x03\xf6\x018\t\x02\x01\x02\x10\x02"},
{"regexp/syntax", "\xba\x02\x01\x01\x01\x02\x10\x02"},
{"runtime", "\x9a\x01\x04\x01\x03\f\x06\a\x02\x01\x01\x0e\x03\x01\x01\x01\x02\x01\x01\x01\x02\x01\x04\x01\x10\x18K"},
{"runtime/coverage", "\xa6\x01S"},
{"runtime/debug", "vUZ\r\b\x02\x01\x10\x06"},
{"runtime/metrics", "\xbd\x01H-!"},
{"runtime/pprof", "\x02\x01\x01\x03\x06_\a\x03$$\x0f\v!\f \r\b\x01\x01\x01\x02\x02\t\x03\x06"},
{"runtime/race", "\xb8\x02"},
{"runtime/race/internal/amd64v1", ""},
{"runtime/trace", "\rd\x03x\t9\b\x05\x01\r\x06"},
{"slices", "\x04\xf4\x01\fK"},
{"sort", "\xcc\x0182"},
{"strconv", "q*@\x01q"},
{"strings", "q&\x04A\x19\x03\f7\x10\x02\x02"},
{"runtime/trace", "\rf\x03z\t9\b\x05\x01\r\x06"},
{"slices", "\x04\xf8\x01\fK"},
{"sort", "\xcf\x0192"},
{"strconv", "s+A\x01q"},
{"strings", "s'\x04B\x19\x03\f7\x10\x02\x02"},
{"structs", ""},
{"sync", "\xcb\x01\x12\x01P\x0e\x13"},
{"sync/atomic", "\xcf\x02"},
{"syscall", "q'\x03\x01\x1c\n\x03\x06\f\x04S\b\x05\x01\x13"},
{"testing", "\x03\nd\x02\x01W\x16\x14\f\x05\x1b\x06\x02\x05\x02\x05\x01\x02\x01\x02\x01\r\x02\x04"},
{"testing/fstest", "q\x03\x81\x01\x01\n&\x10\x03\b\b"},
{"testing/internal/testdeps", "\x02\v\xaa\x01.\x10,\x03\x05\x03\x06\a\x02\x0e"},
{"testing/iotest", "\x03n\x03\x81\x01\x04"},
{"testing/quick", "s\x01\x8d\x01\x05#\x10\x10"},
{"testing/slogtest", "\rd\x03\x87\x01.\x05\x10\v"},
{"testing/synctest", "\xde\x01`\x11"},
{"text/scanner", "\x03q\x81\x01,*\x02"},
{"text/tabwriter", "t\x81\x01X"},
{"text/template", "q\x03B?\x01\n \x01\x05\x01\x02\x05\v\x02\r\x03\x02"},
{"text/template/parse", "\x03q\xba\x01\n\x01\x12\x02"},
{"time", "q*\x1e#(*\r\x02\x12"},
{"time/tzdata", "q\xcc\x01\x12"},
{"sync", "\xce\x01\x13\x01P\x0e\x13"},
{"sync/atomic", "\xd3\x02"},
{"syscall", "s(\x03\x01\x1c\n\x03\x06\r\x04S\b\x05\x01\x13"},
{"testing", "\x03\nf\x02\x01X\x17\x14\f\x05\x1b\x06\x02\x05\x02\x05\x01\x02\x01\x02\x01\r\x02\x04"},
{"testing/fstest", "s\x03\x83\x01\x01\n&\x10\x03\b\b"},
{"testing/internal/testdeps", "\x02\v\xad\x01/\x10,\x03\x05\x03\x06\a\x02\x0e"},
{"testing/iotest", "\x03p\x03\x83\x01\x04"},
{"testing/quick", "u\x01\x8f\x01\x05#\x10\x10"},
{"testing/slogtest", "\rf\x03\x89\x01.\x05\x10\v"},
{"testing/synctest", "\xe2\x01`\x11"},
{"text/scanner", "\x03s\x83\x01,*\x02"},
{"text/tabwriter", "v\x83\x01X"},
{"text/template", "s\x03C@\x01\n \x01\x05\x01\x02\x05\v\x02\r\x03\x02"},
{"text/template/parse", "\x03s\xbc\x01\n\x01\x12\x02"},
{"time", "s+\x1e$(*\r\x02\x12"},
{"time/tzdata", "s\xce\x01\x12"},
{"unicode", ""},
{"unicode/utf16", ""},
{"unicode/utf8", ""},
{"unique", "\x97\x01!$\x01Q\r\x01\x13\x12"},
{"unique", "\x9a\x01!%\x01Q\r\x01\x13\x12"},
{"unsafe", ""},
{"vendor/golang.org/x/crypto/chacha20", "\x10Z\a\x93\x01*&"},
{"vendor/golang.org/x/crypto/chacha20poly1305", "\x10Z\a\xdf\x01\x04\x01\a"},
{"vendor/golang.org/x/crypto/cryptobyte", "g\n\x03\x8e\x01' \n"},
{"vendor/golang.org/x/crypto/chacha20", "\x10\\\a\x95\x01*&"},
{"vendor/golang.org/x/crypto/chacha20poly1305", "\x10\\\a\xe1\x01\x04\x01\a"},
{"vendor/golang.org/x/crypto/cryptobyte", "i\n\x03\x90\x01' \n"},
{"vendor/golang.org/x/crypto/cryptobyte/asn1", ""},
{"vendor/golang.org/x/crypto/internal/alias", "\xcf\x02"},
{"vendor/golang.org/x/crypto/internal/poly1305", "U\x15\x9a\x01"},
{"vendor/golang.org/x/net/dns/dnsmessage", "q"},
{"vendor/golang.org/x/net/http/httpguts", "\x8b\x02\x14\x1a\x14\r"},
{"vendor/golang.org/x/net/http/httpproxy", "q\x03\x97\x01\x10\x05\x01\x18\x14\r"},
{"vendor/golang.org/x/net/http2/hpack", "\x03n\x03\x81\x01F"},
{"vendor/golang.org/x/net/idna", "t\x8d\x018\x14\x10\x02\x01"},
{"vendor/golang.org/x/net/nettest", "\x03g\a\x03\x81\x01\x11\x05\x16\x01\f\n\x01\x02\x02\x01\v"},
{"vendor/golang.org/x/sys/cpu", "\xa1\x02\r\n\x01\x16"},
{"vendor/golang.org/x/text/secure/bidirule", "q\xdc\x01\x11\x01"},
{"vendor/golang.org/x/text/transform", "\x03n\x84\x01X"},
{"vendor/golang.org/x/text/unicode/bidi", "\x03\bi\x85\x01>\x16"},
{"vendor/golang.org/x/text/unicode/norm", "j\n\x81\x01F\x12\x11"},
{"weak", "\x97\x01\x97\x01!"},
{"vendor/golang.org/x/crypto/internal/alias", "\xd3\x02"},
{"vendor/golang.org/x/crypto/internal/poly1305", "W\x15\x9c\x01"},
{"vendor/golang.org/x/net/dns/dnsmessage", "s\xc7\x01"},
{"vendor/golang.org/x/net/http/httpguts", "\x8f\x02\x14\x1a\x14\r"},
{"vendor/golang.org/x/net/http/httpproxy", "s\x03\x99\x01\x10\x05\x01\x18\x14\r"},
{"vendor/golang.org/x/net/http2/hpack", "\x03p\x03\x83\x01F"},
{"vendor/golang.org/x/net/idna", "v\x8f\x018\x14\x10\x02\x01"},
{"vendor/golang.org/x/net/nettest", "\x03i\a\x03\x83\x01\x11\x05\x16\x01\f\n\x01\x02\x02\x01\v"},
{"vendor/golang.org/x/sys/cpu", "\xa5\x02\r\n\x01\x16"},
{"vendor/golang.org/x/text/secure/bidirule", "s\xde\x01\x11\x01"},
{"vendor/golang.org/x/text/transform", "\x03p\x86\x01X"},
{"vendor/golang.org/x/text/unicode/bidi", "\x03\bk\x87\x01>\x16"},
{"vendor/golang.org/x/text/unicode/norm", "l\n\x83\x01F\x12\x11"},
{"weak", "\x9a\x01\x98\x01!"},
}
// bootstrap is the list of bootstrap packages extracted from cmd/dist.
@@ -385,6 +389,7 @@ var bootstrap = map[string]bool{
"cmd/compile/internal/arm64": true,
"cmd/compile/internal/base": true,
"cmd/compile/internal/bitvec": true,
"cmd/compile/internal/bloop": true,
"cmd/compile/internal/compare": true,
"cmd/compile/internal/coverage": true,
"cmd/compile/internal/deadlocals": true,
@@ -413,6 +418,7 @@ var bootstrap = map[string]bool{
"cmd/compile/internal/riscv64": true,
"cmd/compile/internal/rttype": true,
"cmd/compile/internal/s390x": true,
"cmd/compile/internal/slice": true,
"cmd/compile/internal/ssa": true,
"cmd/compile/internal/ssagen": true,
"cmd/compile/internal/staticdata": true,
+533 -16
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -39,7 +39,7 @@ const (
Var // "EOF"
Const // "Pi"
Field // "Point.X"
Method // "(*Buffer).Grow"
Method // "(*Buffer).Grow" or "(Reader).Read"
)
func (kind Kind) String() string {
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"fmt"
"go/ast"
"go/types"
_ "unsafe"
_ "unsafe" // for go:linkname hack
)
// CallKind describes the function position of an [*ast.CallExpr].
+1 -3
View File
@@ -23,7 +23,6 @@ import (
"go/token"
"go/types"
"reflect"
"unsafe"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/aliases"
@@ -40,8 +39,7 @@ func SetUsesCgo(conf *types.Config) bool {
}
}
addr := unsafe.Pointer(f.UnsafeAddr())
*(*bool)(addr) = true
*(*bool)(f.Addr().UnsafePointer()) = true
return true
}
+1
View File
@@ -9,6 +9,7 @@ package versions
// named constants, to avoid misspelling
const (
Go1_17 = "go1.17"
Go1_18 = "go1.18"
Go1_19 = "go1.19"
Go1_20 = "go1.20"
+9 -2
View File
@@ -32,7 +32,7 @@ var byteType = reflect.TypeOf(byte(0))
func Unmarshal(tag string, goType reflect.Type, evs protoreflect.EnumValueDescriptors) protoreflect.FieldDescriptor {
f := new(filedesc.Field)
f.L0.ParentFile = filedesc.SurrogateProto2
f.L1.EditionFeatures = f.L0.ParentFile.L1.EditionFeatures
packed := false
for len(tag) > 0 {
i := strings.IndexByte(tag, ',')
if i < 0 {
@@ -108,7 +108,7 @@ func Unmarshal(tag string, goType reflect.Type, evs protoreflect.EnumValueDescri
f.L1.StringName.InitJSON(jsonName)
}
case s == "packed":
f.L1.EditionFeatures.IsPacked = true
packed = true
case strings.HasPrefix(s, "def="):
// The default tag is special in that everything afterwards is the
// default regardless of the presence of commas.
@@ -121,6 +121,13 @@ func Unmarshal(tag string, goType reflect.Type, evs protoreflect.EnumValueDescri
tag = strings.TrimPrefix(tag[i:], ",")
}
// Update EditionFeatures after the loop and after we know whether this is
// a proto2 or proto3 field.
f.L1.EditionFeatures = f.L0.ParentFile.L1.EditionFeatures
if packed {
f.L1.EditionFeatures.IsPacked = true
}
// The generator uses the group message name instead of the field name.
// We obtain the real field name by lowercasing the group name.
if f.L1.Kind == protoreflect.GroupKind {
+82 -39
View File
@@ -424,27 +424,34 @@ func (d *Decoder) parseFieldName() (tok Token, err error) {
return Token{}, d.newSyntaxError("invalid field name: %s", errId(d.in))
}
// parseTypeName parses Any type URL or extension field name. The name is
// enclosed in [ and ] characters. The C++ parser does not handle many legal URL
// strings. This implementation is more liberal and allows for the pattern
// ^[-_a-zA-Z0-9]+([./][-_a-zA-Z0-9]+)*`). Whitespaces and comments are allowed
// in between [ ], '.', '/' and the sub names.
// parseTypeName parses an Any type URL or an extension field name. The name is
// enclosed in [ and ] characters. We allow almost arbitrary type URL prefixes,
// closely following the text-format spec [1,2]. We implement "ExtensionName |
// AnyName" as follows (with some exceptions for backwards compatibility):
//
// char = [-_a-zA-Z0-9]
// url_char = char | [.~!$&'()*+,;=] | "%", hex, hex
//
// Ident = char, { char }
// TypeName = Ident, { ".", Ident } ;
// UrlPrefix = url_char, { url_char | "/" } ;
// ExtensionName = "[", TypeName, "]" ;
// AnyName = "[", UrlPrefix, "/", TypeName, "]" ;
//
// Additionally, we allow arbitrary whitespace and comments between [ and ].
//
// [1] https://protobuf.dev/reference/protobuf/textformat-spec/#characters
// [2] https://protobuf.dev/reference/protobuf/textformat-spec/#field-names
func (d *Decoder) parseTypeName() (Token, error) {
startPos := len(d.orig) - len(d.in)
// Use alias s to advance first in order to use d.in for error handling.
// Caller already checks for [ as first character.
// Caller already checks for [ as first character (d.in[0] == '[').
s := consume(d.in[1:], 0)
if len(s) == 0 {
return Token{}, ErrUnexpectedEOF
}
// Collect everything between [ and ] in name.
var name []byte
for len(s) > 0 && isTypeNameChar(s[0]) {
name = append(name, s[0])
s = s[1:]
}
s = consume(s, 0)
var closed bool
for len(s) > 0 && !closed {
switch {
@@ -452,23 +459,20 @@ func (d *Decoder) parseTypeName() (Token, error) {
s = s[1:]
closed = true
case s[0] == '/', s[0] == '.':
if len(name) > 0 && (name[len(name)-1] == '/' || name[len(name)-1] == '.') {
return Token{}, d.newSyntaxError("invalid type URL/extension field name: %s",
d.orig[startPos:len(d.orig)-len(s)+1])
}
case s[0] == '/' || isTypeNameChar(s[0]) || isUrlExtraChar(s[0]):
name = append(name, s[0])
s = s[1:]
s = consume(s, 0)
for len(s) > 0 && isTypeNameChar(s[0]) {
name = append(name, s[0])
s = s[1:]
s = consume(s[1:], 0)
// URL percent-encoded chars
case s[0] == '%':
if len(s) < 3 || !isHexChar(s[1]) || !isHexChar(s[2]) {
return Token{}, d.parseTypeNameError(s, 3)
}
s = consume(s, 0)
name = append(name, s[0], s[1], s[2])
s = consume(s[3:], 0)
default:
return Token{}, d.newSyntaxError(
"invalid type URL/extension field name: %s", d.orig[startPos:len(d.orig)-len(s)+1])
return Token{}, d.parseTypeNameError(s, 1)
}
}
@@ -476,15 +480,38 @@ func (d *Decoder) parseTypeName() (Token, error) {
return Token{}, ErrUnexpectedEOF
}
// First character cannot be '.'. Last character cannot be '.' or '/'.
size := len(name)
if size == 0 || name[0] == '.' || name[size-1] == '.' || name[size-1] == '/' {
return Token{}, d.newSyntaxError("invalid type URL/extension field name: %s",
d.orig[startPos:len(d.orig)-len(s)])
// Split collected name on last '/' into urlPrefix and typeName (if '/' is
// present).
typeName := name
if i := bytes.LastIndexByte(name, '/'); i != -1 {
urlPrefix := name[:i]
typeName = name[i+1:]
// urlPrefix may be empty (for backwards compatibility).
// If non-empty, it must not start with '/'.
if len(urlPrefix) > 0 && urlPrefix[0] == '/' {
return Token{}, d.parseTypeNameError(s, 0)
}
}
// typeName must not be empty (note: "" splits to [""]) and all identifier
// parts must not be empty.
for _, ident := range bytes.Split(typeName, []byte{'.'}) {
if len(ident) == 0 {
return Token{}, d.parseTypeNameError(s, 0)
}
}
// typeName must not contain any percent-encoded or special URL chars.
for _, b := range typeName {
if b == '%' || (b != '.' && isUrlExtraChar(b)) {
return Token{}, d.parseTypeNameError(s, 0)
}
}
startPos := len(d.orig) - len(d.in)
endPos := len(d.orig) - len(s)
d.in = s
endPos := len(d.orig) - len(d.in)
d.consume(0)
return Token{
@@ -496,16 +523,32 @@ func (d *Decoder) parseTypeName() (Token, error) {
}, nil
}
func isTypeNameChar(b byte) bool {
return (b == '-' || b == '_' ||
('0' <= b && b <= '9') ||
('a' <= b && b <= 'z') ||
('A' <= b && b <= 'Z'))
func (d *Decoder) parseTypeNameError(s []byte, numUnconsumedChars int) error {
return d.newSyntaxError(
"invalid type URL/extension field name: %s",
d.in[:len(d.in)-len(s)+min(numUnconsumedChars, len(s))],
)
}
func isWhiteSpace(b byte) bool {
func isHexChar(b byte) bool {
return ('0' <= b && b <= '9') ||
('a' <= b && b <= 'f') ||
('A' <= b && b <= 'F')
}
func isTypeNameChar(b byte) bool {
return b == '-' || b == '_' ||
('0' <= b && b <= '9') ||
('a' <= b && b <= 'z') ||
('A' <= b && b <= 'Z')
}
// isUrlExtraChar complements isTypeNameChar with extra characters that we allow
// in URLs but not in type names. Note that '/' is not included so that it can
// be treated specially.
func isUrlExtraChar(b byte) bool {
switch b {
case ' ', '\n', '\r', '\t':
case '.', '~', '!', '$', '&', '(', ')', '*', '+', ',', ';', '=':
return true
default:
return false
+1
View File
@@ -32,6 +32,7 @@ const (
EditionProto3 Edition = 999
Edition2023 Edition = 1000
Edition2024 Edition = 1001
EditionUnstable Edition = 9999
EditionUnsupported Edition = 100000
)
-22
View File
@@ -330,7 +330,6 @@ func (md *Message) unmarshalFull(b []byte, sb *strs.Builder) {
md.L1.Extensions.List[extensionIdx].unmarshalFull(v, sb)
extensionIdx++
case genid.DescriptorProto_Options_field_number:
md.unmarshalOptions(v)
rawOptions = appendOptions(rawOptions, v)
}
default:
@@ -356,27 +355,6 @@ func (md *Message) unmarshalFull(b []byte, sb *strs.Builder) {
md.L2.Options = md.L0.ParentFile.builder.optionsUnmarshaler(&descopts.Message, rawOptions)
}
func (md *Message) unmarshalOptions(b []byte) {
for len(b) > 0 {
num, typ, n := protowire.ConsumeTag(b)
b = b[n:]
switch typ {
case protowire.VarintType:
v, m := protowire.ConsumeVarint(b)
b = b[m:]
switch num {
case genid.MessageOptions_MapEntry_field_number:
md.L1.IsMapEntry = protowire.DecodeBool(v)
case genid.MessageOptions_MessageSetWireFormat_field_number:
md.L1.IsMessageSet = protowire.DecodeBool(v)
}
default:
m := protowire.ConsumeFieldValue(num, typ, b)
b = b[m:]
}
}
}
func unmarshalMessageReservedRange(b []byte) (r [2]protoreflect.FieldNumber) {
for len(b) > 0 {
num, typ, n := protowire.ConsumeTag(b)
+1
View File
@@ -26,6 +26,7 @@ const (
Edition_EDITION_PROTO3_enum_value = 999
Edition_EDITION_2023_enum_value = 1000
Edition_EDITION_2024_enum_value = 1001
Edition_EDITION_UNSTABLE_enum_value = 9999
Edition_EDITION_1_TEST_ONLY_enum_value = 1
Edition_EDITION_2_TEST_ONLY_enum_value = 2
Edition_EDITION_99997_TEST_ONLY_enum_value = 99997
+6
View File
@@ -113,6 +113,9 @@ func sizeMap(mapv reflect.Value, mapi *mapInfo, f *coderFieldInfo, opts marshalO
}
func consumeMap(b []byte, mapv reflect.Value, wtyp protowire.Type, mapi *mapInfo, f *coderFieldInfo, opts unmarshalOptions) (out unmarshalOutput, err error) {
if opts.depth--; opts.depth < 0 {
return out, errRecursionDepth
}
if wtyp != protowire.BytesType {
return out, errUnknown
}
@@ -170,6 +173,9 @@ func consumeMap(b []byte, mapv reflect.Value, wtyp protowire.Type, mapi *mapInfo
}
func consumeMapOfMessage(b []byte, mapv reflect.Value, wtyp protowire.Type, mapi *mapInfo, f *coderFieldInfo, opts unmarshalOptions) (out unmarshalOutput, err error) {
if opts.depth--; opts.depth < 0 {
return out, errRecursionDepth
}
if wtyp != protowire.BytesType {
return out, errUnknown
}
+1 -2
View File
@@ -102,8 +102,7 @@ var errUnknown = errors.New("unknown")
func (mi *MessageInfo) unmarshalPointer(b []byte, p pointer, groupTag protowire.Number, opts unmarshalOptions) (out unmarshalOutput, err error) {
mi.init()
opts.depth--
if opts.depth < 0 {
if opts.depth--; opts.depth < 0 {
return out, errRecursionDepth
}
if flags.ProtoLegacy && mi.isMessageSet {
+26
View File
@@ -68,9 +68,13 @@ func Validate(mt protoreflect.MessageType, in protoiface.UnmarshalInput) (out pr
if in.Resolver == nil {
in.Resolver = protoregistry.GlobalTypes
}
if in.Depth == 0 {
in.Depth = protowire.DefaultRecursionLimit
}
o, st := mi.validate(in.Buf, 0, unmarshalOptions{
flags: in.Flags,
resolver: in.Resolver,
depth: in.Depth,
})
if o.initialized {
out.Flags |= protoiface.UnmarshalInitialized
@@ -257,6 +261,9 @@ func (mi *MessageInfo) validate(b []byte, groupTag protowire.Number, opts unmars
states[0].typ = validationTypeGroup
states[0].endGroup = groupTag
}
if opts.depth--; opts.depth < 0 {
return out, ValidationInvalid
}
initialized := true
start := len(b)
State:
@@ -451,6 +458,13 @@ State:
mi: vi.mi,
tail: b,
})
if vi.typ == validationTypeMessage ||
vi.typ == validationTypeGroup ||
vi.typ == validationTypeMap {
if opts.depth--; opts.depth < 0 {
return out, ValidationInvalid
}
}
b = v
continue State
case validationTypeRepeatedVarint:
@@ -499,6 +513,9 @@ State:
mi: vi.mi,
endGroup: num,
})
if opts.depth--; opts.depth < 0 {
return out, ValidationInvalid
}
continue State
case flags.ProtoLegacy && vi.typ == validationTypeMessageSetItem:
typeid, v, n, err := messageset.ConsumeFieldValue(b, false)
@@ -521,6 +538,13 @@ State:
mi: xvi.mi,
tail: b[n:],
})
if xvi.typ == validationTypeMessage ||
xvi.typ == validationTypeGroup ||
xvi.typ == validationTypeMap {
if opts.depth--; opts.depth < 0 {
return out, ValidationInvalid
}
}
b = v
continue State
}
@@ -547,12 +571,14 @@ State:
switch st.typ {
case validationTypeMessage, validationTypeGroup:
numRequiredFields = int(st.mi.numRequiredFields)
opts.depth++
case validationTypeMap:
// If this is a map field with a message value that contains
// required fields, require that the value be present.
if st.mi != nil && st.mi.numRequiredFields > 0 {
numRequiredFields = 1
}
opts.depth++
}
// If there are more than 64 required fields, this check will
// always fail and we will report that the message is potentially
+1 -1
View File
@@ -52,7 +52,7 @@ import (
const (
Major = 1
Minor = 36
Patch = 10
Patch = 11
PreRelease = ""
)
+7 -3
View File
@@ -121,9 +121,8 @@ func (o UnmarshalOptions) unmarshal(b []byte, m protoreflect.Message) (out proto
out, err = methods.Unmarshal(in)
} else {
o.RecursionLimit--
if o.RecursionLimit < 0 {
return out, errors.New("exceeded max recursion depth")
if o.RecursionLimit--; o.RecursionLimit < 0 {
return out, errRecursionDepth
}
err = o.unmarshalMessageSlow(b, m)
}
@@ -220,6 +219,9 @@ func (o UnmarshalOptions) unmarshalSingular(b []byte, wtyp protowire.Type, m pro
}
func (o UnmarshalOptions) unmarshalMap(b []byte, wtyp protowire.Type, mapv protoreflect.Map, fd protoreflect.FieldDescriptor) (n int, err error) {
if o.RecursionLimit--; o.RecursionLimit < 0 {
return 0, errRecursionDepth
}
if wtyp != protowire.BytesType {
return 0, errUnknown
}
@@ -305,3 +307,5 @@ func (o UnmarshalOptions) unmarshalMap(b []byte, wtyp protowire.Type, mapv proto
var errUnknown = errors.New("BUG: internal error (unknown)")
var errDecode = errors.New("cannot parse invalid wire-format data")
var errRecursionDepth = errors.New("exceeded maximum recursion depth")
+8 -3
View File
@@ -108,7 +108,9 @@ func (o FileOptions) New(fd *descriptorpb.FileDescriptorProto, r Resolver) (prot
if f.L1.Path == "" {
return nil, errors.New("file path must be populated")
}
if f.L1.Syntax == protoreflect.Editions && (fd.GetEdition() < editionssupport.Minimum || fd.GetEdition() > editionssupport.Maximum) {
if f.L1.Syntax == protoreflect.Editions &&
(fd.GetEdition() < editionssupport.Minimum || fd.GetEdition() > editionssupport.Maximum) &&
fd.GetEdition() != descriptorpb.Edition_EDITION_UNSTABLE {
// Allow cmd/protoc-gen-go/testdata to use any edition for easier
// testing of upcoming edition features.
if !strings.HasPrefix(fd.GetName(), "cmd/protoc-gen-go/testdata/") {
@@ -152,6 +154,7 @@ func (o FileOptions) New(fd *descriptorpb.FileDescriptorProto, r Resolver) (prot
imp := &f.L2.Imports[i]
imps.importPublic(imp.Imports())
}
optionImps := importSet{f.Path(): true}
if len(fd.GetOptionDependency()) > 0 {
optionImports := make(filedesc.FileImports, len(fd.GetOptionDependency()))
for i, path := range fd.GetOptionDependency() {
@@ -165,10 +168,12 @@ func (o FileOptions) New(fd *descriptorpb.FileDescriptorProto, r Resolver) (prot
}
imp.FileDescriptor = f
if imps[imp.Path()] {
if imps[imp.Path()] || optionImps[imp.Path()] {
return nil, errors.New("already imported %q", path)
}
imps[imp.Path()] = true
// This needs to be a separate map so that we don't recognize non-options
// symbols coming from option imports.
optionImps[imp.Path()] = true
}
f.L2.OptionImports = func() protoreflect.FileImports {
return &optionImports
+3 -1
View File
@@ -46,6 +46,8 @@ func toEditionProto(ed filedesc.Edition) descriptorpb.Edition {
return descriptorpb.Edition_EDITION_2023
case filedesc.Edition2024:
return descriptorpb.Edition_EDITION_2024
case filedesc.EditionUnstable:
return descriptorpb.Edition_EDITION_UNSTABLE
default:
panic(fmt.Sprintf("unknown value for edition: %v", ed))
}
@@ -58,7 +60,7 @@ func getFeatureSetFor(ed filedesc.Edition) *descriptorpb.FeatureSet {
return def
}
edpb := toEditionProto(ed)
if defaults.GetMinimumEdition() > edpb || defaults.GetMaximumEdition() < edpb {
if (defaults.GetMinimumEdition() > edpb || defaults.GetMaximumEdition() < edpb) && edpb != descriptorpb.Edition_EDITION_UNSTABLE {
// This should never happen protodesc.(FileOptions).New would fail when
// initializing the file descriptor.
// This most likely means the embedded defaults were not updated.
+9 -4
View File
@@ -69,6 +69,8 @@ const (
// comparison.
Edition_EDITION_2023 Edition = 1000
Edition_EDITION_2024 Edition = 1001
// A placeholder edition for developing and testing unscheduled features.
Edition_EDITION_UNSTABLE Edition = 9999
// Placeholder editions for testing feature resolution. These should not be
// used or relied on outside of tests.
Edition_EDITION_1_TEST_ONLY Edition = 1
@@ -91,6 +93,7 @@ var (
999: "EDITION_PROTO3",
1000: "EDITION_2023",
1001: "EDITION_2024",
9999: "EDITION_UNSTABLE",
1: "EDITION_1_TEST_ONLY",
2: "EDITION_2_TEST_ONLY",
99997: "EDITION_99997_TEST_ONLY",
@@ -105,6 +108,7 @@ var (
"EDITION_PROTO3": 999,
"EDITION_2023": 1000,
"EDITION_2024": 1001,
"EDITION_UNSTABLE": 9999,
"EDITION_1_TEST_ONLY": 1,
"EDITION_2_TEST_ONLY": 2,
"EDITION_99997_TEST_ONLY": 99997,
@@ -4793,11 +4797,11 @@ const file_google_protobuf_descriptor_proto_rawDesc = "" +
"\x18EnumValueDescriptorProto\x12\x12\n" +
"\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n" +
"\x06number\x18\x02 \x01(\x05R\x06number\x12;\n" +
"\aoptions\x18\x03 \x01(\v2!.google.protobuf.EnumValueOptionsR\aoptions\"\xa7\x01\n" +
"\aoptions\x18\x03 \x01(\v2!.google.protobuf.EnumValueOptionsR\aoptions\"\xb5\x01\n" +
"\x16ServiceDescriptorProto\x12\x12\n" +
"\x04name\x18\x01 \x01(\tR\x04name\x12>\n" +
"\x06method\x18\x02 \x03(\v2&.google.protobuf.MethodDescriptorProtoR\x06method\x129\n" +
"\aoptions\x18\x03 \x01(\v2\x1f.google.protobuf.ServiceOptionsR\aoptions\"\x89\x02\n" +
"\aoptions\x18\x03 \x01(\v2\x1f.google.protobuf.ServiceOptionsR\aoptionsJ\x04\b\x04\x10\x05R\x06stream\"\x89\x02\n" +
"\x15MethodDescriptorProto\x12\x12\n" +
"\x04name\x18\x01 \x01(\tR\x04name\x12\x1d\n" +
"\n" +
@@ -5033,14 +5037,15 @@ const file_google_protobuf_descriptor_proto_rawDesc = "" +
"\bSemantic\x12\b\n" +
"\x04NONE\x10\x00\x12\a\n" +
"\x03SET\x10\x01\x12\t\n" +
"\x05ALIAS\x10\x02*\xa7\x02\n" +
"\x05ALIAS\x10\x02*\xbe\x02\n" +
"\aEdition\x12\x13\n" +
"\x0fEDITION_UNKNOWN\x10\x00\x12\x13\n" +
"\x0eEDITION_LEGACY\x10\x84\a\x12\x13\n" +
"\x0eEDITION_PROTO2\x10\xe6\a\x12\x13\n" +
"\x0eEDITION_PROTO3\x10\xe7\a\x12\x11\n" +
"\fEDITION_2023\x10\xe8\a\x12\x11\n" +
"\fEDITION_2024\x10\xe9\a\x12\x17\n" +
"\fEDITION_2024\x10\xe9\a\x12\x15\n" +
"\x10EDITION_UNSTABLE\x10\x8fN\x12\x17\n" +
"\x13EDITION_1_TEST_ONLY\x10\x01\x12\x17\n" +
"\x13EDITION_2_TEST_ONLY\x10\x02\x12\x1d\n" +
"\x17EDITION_99997_TEST_ONLY\x10\x9d\x8d\x06\x12\x1d\n" +
+7 -6
View File
@@ -172,13 +172,14 @@ import (
// ) to obtain a formatter capable of generating timestamps in this format.
type Timestamp struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Represents seconds of UTC time since Unix epoch
// 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
// 9999-12-31T23:59:59Z inclusive.
// Represents seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z. Must
// be between -315576000000 and 315576000000 inclusive (which corresponds to
// 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z).
Seconds int64 `protobuf:"varint,1,opt,name=seconds,proto3" json:"seconds,omitempty"`
// Non-negative fractions of a second at nanosecond resolution. Negative
// second values with fractions must still have non-negative nanos values
// that count forward in time. Must be from 0 to 999,999,999
// Non-negative fractions of a second at nanosecond resolution. This field is
// the nanosecond portion of the duration, not an alternative to seconds.
// Negative second values with fractions must still have non-negative nanos
// values that count forward in time. Must be between 0 and 999,999,999
// inclusive.
Nanos int32 `protobuf:"varint,2,opt,name=nanos,proto3" json:"nanos,omitempty"`
unknownFields protoimpl.UnknownFields
+14 -8
View File
@@ -24,7 +24,7 @@ github.com/Binject/binjection/bj
github.com/Binject/debug/elf
github.com/Binject/debug/macho
github.com/Binject/debug/pe
# github.com/Binject/go-donut v0.0.0-20220908180326-fcdcc35d591c
# github.com/Binject/go-donut v0.0.0-20210701074227-67a31e2d883e
## explicit; go 1.16
github.com/Binject/go-donut/donut
# github.com/Binject/shellcode v0.0.0-20191101084904-a8a90e7d4563
@@ -286,6 +286,9 @@ github.com/coder/websocket/internal/wsjs
github.com/coder/websocket/internal/xsync
# github.com/coreos/go-iptables v0.8.0
## explicit; go 1.16
# github.com/creachadair/msync v0.7.1
## explicit; go 1.25
github.com/creachadair/msync/trigger
# github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
## explicit
github.com/davecgh/go-spew/spew
@@ -519,7 +522,7 @@ github.com/mdlayher/netlink/nlenc
# github.com/mdlayher/socket v0.5.1
## explicit; go 1.20
github.com/mdlayher/socket
# github.com/miekg/dns v1.1.69
# github.com/miekg/dns v1.1.70
## explicit; go 1.24.0
github.com/miekg/dns
# github.com/mitchellh/go-ps v1.0.0
@@ -569,6 +572,9 @@ github.com/ncruces/julianday
# github.com/olekukonko/tablewriter v0.0.5
## explicit; go 1.12
github.com/olekukonko/tablewriter
# github.com/pires/go-proxyproto v0.8.1
## explicit; go 1.24
github.com/pires/go-proxyproto
# github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2
## explicit
github.com/pmezard/go-difflib/difflib
@@ -822,7 +828,7 @@ golang.org/x/mod/internal/lazyregexp
golang.org/x/mod/modfile
golang.org/x/mod/module
golang.org/x/mod/semver
# golang.org/x/net v0.47.0
# golang.org/x/net v0.48.0
## explicit; go 1.24.0
golang.org/x/net/bpf
golang.org/x/net/dns/dnsmessage
@@ -851,7 +857,7 @@ golang.org/x/oauth2/internal
## explicit; go 1.24.0
golang.org/x/sync/errgroup
golang.org/x/sync/semaphore
# golang.org/x/sys v0.39.0
# golang.org/x/sys v0.40.0
## explicit; go 1.24.0
golang.org/x/sys/cpu
golang.org/x/sys/plan9
@@ -886,7 +892,7 @@ golang.org/x/text/width
# golang.org/x/time v0.13.0
## explicit; go 1.24.0
golang.org/x/time/rate
# golang.org/x/tools v0.39.0
# golang.org/x/tools v0.40.0
## explicit; go 1.24.0
golang.org/x/tools/go/ast/edge
golang.org/x/tools/go/ast/inspector
@@ -994,7 +1000,7 @@ google.golang.org/grpc/stats
google.golang.org/grpc/status
google.golang.org/grpc/tap
google.golang.org/grpc/test/bufconn
# google.golang.org/protobuf v1.36.10
# google.golang.org/protobuf v1.36.11
## explicit; go 1.23
google.golang.org/protobuf/encoding/protojson
google.golang.org/protobuf/encoding/prototext
@@ -1192,8 +1198,8 @@ modernc.org/sqlite/lib
## explicit; go 1.23.0
mvdan.cc/sh/v3/fileutil
mvdan.cc/sh/v3/syntax
# tailscale.com v1.90.6
## explicit; go 1.25.3
# tailscale.com v1.92.5
## explicit; go 1.25.5
tailscale.com
tailscale.com/appc
tailscale.com/atomicfile
+56 -100
View File
@@ -1,147 +1,103 @@
# Contributor Covenant Code of Conduct
# Tailscale Community Code of Conduct
## Our Pledge
We are committed to creating an open, welcoming, diverse, inclusive,
healthy and respectful community.
We are committed to creating an open, welcoming, diverse, inclusive, healthy and respectful community.
Unacceptable, harmful and inappropriate behavior will not be tolerated.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people.
* Being respectful of differing opinions, viewpoints, and experiences.
* Giving and gracefully accepting constructive feedback.
* Accepting responsibility and apologizing to those affected by our
mistakes, and learning from the experience.
* Focusing on what is best not just for us as individuals, but for the
overall community.
Examples of behavior that contributes to a positive environment for our community include:
- Demonstrating empathy and kindness toward other people.
- Being respectful of differing opinions, viewpoints, and experiences.
- Giving and gracefully accepting constructive feedback.
- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience.
- Focusing on what is best not just for us as individuals, but for the overall community.
Examples of unacceptable behavior include without limitation:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind.
* The use of violent, intimidating or bullying language or imagery.
* Trolling, insulting or derogatory comments, and personal or
political attacks.
* Public or private harassment.
* Publishing others' private information, such as a physical or email
address, without their explicit permission.
* Spamming community channels and members, such as sending repeat messages,
low-effort content, or automated messages.
* Phishing or any similar activity;
* Distributing or promoting malware;
* Other conduct which could reasonably be considered inappropriate in a
professional setting.
Please also see the Tailscale Acceptable Use Policy, available at
[tailscale.com/tailscale-aup](https://tailscale.com/tailscale-aup).
- The use of language, imagery or emojis (collectively "content") that is racist, sexist, homophobic, transphobic, or otherwise harassing or discriminatory based on any protected characteristic.
- The use of sexualized content and sexual attention or advances of any kind.
- The use of violent, intimidating or bullying content.
- Trolling, concern trolling, insulting or derogatory comments, and personal or political attacks.
- Public or private harassment.
- Publishing others' personal information, such as a photo, physical address, email address, online profile information, or other personal information, without their explicit permission or with the intent to bully or harass the other person.
- Posting deep fake or other AI generated content about or involving another person without the explicit permission.
- Spamming community channels and members, such as sending repeat messages, low-effort content, or automated messages.
- Phishing or any similar activity.
- Distributing or promoting malware.
- The use of any coded or suggestive content to hide or provoke otherwise unacceptable behavior.
- Other conduct which could reasonably be considered harmful, illegal, or inappropriate in a professional setting.
# Reporting Incidents
Please also see the Tailscale Acceptable Use Policy, available at [tailscale.com/tailscale-aup](https://tailscale.com/tailscale-aup).
Instances of abusive, harassing, or otherwise unacceptable behavior
may be reported to Tailscale directly via info@tailscale.com, or to
the community leaders or moderators via DM or similar.
## Reporting Incidents
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to Tailscale directly via <info@tailscale.com>, or to the community leaders or moderators via DM or similar.
All complaints will be reviewed and investigated promptly and fairly.
We will respect the privacy and safety of the reporter of any issues.
Please note that this community is not moderated by staff 24/7, and we
do not have, and do not undertake, any obligation to prescreen, monitor,
edit, or remove any content or data, or to actively seek facts or
circumstances indicating illegal activity. While we strive to keep the
community safe and welcoming, moderation may not be immediate at all hours.
Please note that this community is not moderated by staff 24/7, and we do not have, and do not undertake, any obligation to prescreen, monitor, edit, or remove any content or data, or to actively seek facts or circumstances indicating illegal activity.
While we strive to keep the community safe and welcoming, moderation may not be immediate at all hours.
If you encounter any issues, report them using the appropriate channels.
## Enforcement
Community leaders and moderators are responsible for clarifying and
enforcing our standards of acceptable behavior and will take appropriate
and fair corrective action in response to any behavior that they deem
inappropriate, threatening, offensive, or harmful.
Community leaders and moderators have the right and responsibility to remove,
edit, or reject comments, commits, code, wiki edits, issues, and other
contributions that are not aligned to this Community Code of Conduct.
Tailscale retains full discretion to take action (or not) in response
to a violation of these guidelines with or without notice or liability
to you. We will interpret our policies and resolve disputes in favor of
protecting users, customers, the public, our community and our company,
as a whole.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in
determining the consequences for any action they deem in violation of
this Code of Conduct:
Community leaders and moderators are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
Community leaders and moderators have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Community Code of Conduct.
Tailscale retains full discretion to take action (or not) in response to a violation of these guidelines with or without notice or liability to you.
We will interpret our policies and resolve disputes in favor of protecting users, customers, the public, our community and our company, as a whole.
Community leaders will follow these community enforcement guidelines in determining the consequences for any action they deem in violation of this Code of Conduct,
and retain full discretion to apply the enforcement guidelines as necessary depending on the circumstances:
### 1. Correction
Community Impact: Use of inappropriate language or other behavior
deemed unprofessional or unwelcome in the community.
Community Impact: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
Consequence: A private, written warning from community leaders,
providing clarity around the nature of the violation and an
explanation of why the behavior was inappropriate. A public apology
may be requested.
Consequence: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate.
A public apology may be requested.
### 2. Warning
Community Impact: A violation through a single incident or series
of actions.
Community Impact: A violation through a single incident or series of actions.
Consequence: A warning with consequences for continued
behavior. No interaction with the people involved, including
unsolicited interaction with those enforcing this Community Code of Conduct,
for a specified period of time. This includes avoiding interactions in
community spaces as well as external channels like social
media. Violating these terms may lead to a temporary or permanent ban.
Consequence: A warning with consequences for continued behavior.
No interaction with the people involved, including unsolicited interaction with those enforcing this Community Code of Conduct, for a specified period of time.
This includes avoiding interactions in community spaces as well as external channels like social media.
Violating these terms may lead to a temporary or permanent ban.
### 3. Temporary Ban
Community Impact: A serious violation of community standards,
including sustained inappropriate behavior.
Community Impact: A serious violation of community standards, including sustained inappropriate behavior.
Consequence: A temporary ban from any sort of interaction or
public communication with the community for a specified period of
time. No public or private interaction with the people involved,
including unsolicited interaction with those enforcing the Code of Conduct,
is allowed during this period. Violating these terms may lead to a permanent ban.
Consequence: A temporary ban from any sort of interaction or public communication with the community for a specified period of time.
No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
Community Impact: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of
an individual, or aggression toward or disparagement of
classes of individuals.
Community Impact: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
Consequence: A permanent ban from any sort of public interaction
within the community.
Consequence: A permanent ban from any sort of public interaction within the community.
## Acceptable Use Policy
Violation of this Community Code of Conduct may also violate the
Tailscale Acceptable Use Policy, which may result in suspension or
termination of your Tailscale account. For more information, please
see the Tailscale Acceptable Use Policy, available at
[tailscale.com/tailscale-aup](https://tailscale.com/tailscale-aup).
Violation of this Community Code of Conduct may also violate the Tailscale Acceptable Use Policy, which may result in suspension or termination of your Tailscale account.
For more information, please see the Tailscale Acceptable Use Policy, available at [tailscale.com/tailscale-aup](https://tailscale.com/tailscale-aup).
## Privacy
Please see the Tailscale [Privacy Policy](http://tailscale.com/privacy-policy)
for more information about how Tailscale collects, uses, discloses and protects
information.
Please see the Tailscale [Privacy Policy](https://tailscale.com/privacy-policy) for more information about how Tailscale collects, uses, discloses and protects information.
## Attribution
This Code of Conduct is adapted from the [Contributor
Covenant][homepage], version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at <https://www.contributor-covenant.org/version/2/0/code_of_conduct.html>.
Community Impact Guidelines were inspired by [Mozilla's code of
conduct enforcement ladder](https://github.com/mozilla/diversity).
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the
FAQ at https://www.contributor-covenant.org/faq. Translations are
available at https://www.contributor-covenant.org/translations.
For answers to common questions about this code of conduct, see the FAQ at <https://www.contributor-covenant.org/faq>.
Translations are available at <https://www.contributor-covenant.org/translations>.
+1 -1
View File
@@ -1 +1 @@
1.90.6
1.92.5
+9 -9
View File
@@ -16,9 +16,9 @@ import (
"net/netip"
"slices"
"strings"
"sync"
"time"
"tailscale.com/syncs"
"tailscale.com/types/appctype"
"tailscale.com/types/logger"
"tailscale.com/types/views"
@@ -139,7 +139,7 @@ type AppConnector struct {
hasStoredRoutes bool
// mu guards the fields that follow
mu sync.Mutex
mu syncs.Mutex
// domains is a map of lower case domain names with no trailing dot, to an
// ordered list of resolved IP addresses.
@@ -203,12 +203,12 @@ func NewAppConnector(c Config) *AppConnector {
ac.wildcards = c.RouteInfo.Wildcards
ac.controlRoutes = c.RouteInfo.Control
}
ac.writeRateMinute = newRateLogger(time.Now, time.Minute, func(c int64, s time.Time, l int64) {
ac.logf("routeInfo write rate: %d in minute starting at %v (%d routes)", c, s, l)
metricStoreRoutes(c, l)
ac.writeRateMinute = newRateLogger(time.Now, time.Minute, func(c int64, s time.Time, ln int64) {
ac.logf("routeInfo write rate: %d in minute starting at %v (%d routes)", c, s, ln)
metricStoreRoutes(c, ln)
})
ac.writeRateDay = newRateLogger(time.Now, 24*time.Hour, func(c int64, s time.Time, l int64) {
ac.logf("routeInfo write rate: %d in 24 hours starting at %v (%d routes)", c, s, l)
ac.writeRateDay = newRateLogger(time.Now, 24*time.Hour, func(c int64, s time.Time, ln int64) {
ac.logf("routeInfo write rate: %d in 24 hours starting at %v (%d routes)", c, s, ln)
})
return ac
}
@@ -510,8 +510,8 @@ func (e *AppConnector) addDomainAddrLocked(domain string, addr netip.Addr) {
slices.SortFunc(e.domains[domain], compareAddr)
}
func compareAddr(l, r netip.Addr) int {
return l.Compare(r)
func compareAddr(a, b netip.Addr) int {
return a.Compare(b)
}
// routesWithout returns a without b where a and b
+61
View File
@@ -0,0 +1,61 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package appc
import (
"errors"
"net/netip"
"go4.org/netipx"
)
// errPoolExhausted is returned when there are no more addresses to iterate over.
var errPoolExhausted = errors.New("ip pool exhausted")
// ippool allows for iteration over all the addresses within a netipx.IPSet.
// netipx.IPSet has a Ranges call that returns the "minimum and sorted set of IP ranges that covers [the set]".
// netipx.IPRange is "an inclusive range of IP addresses from the same address family.". So we can iterate over
// all the addresses in the set by keeping a track of the last address we returned, calling Next on the last address
// to get the new one, and if we run off the edge of the current range, starting on the next one.
type ippool struct {
// ranges defines the addresses in the pool
ranges []netipx.IPRange
// last is internal tracking of which the last address provided was.
last netip.Addr
// rangeIdx is internal tracking of which netipx.IPRange from the IPSet we are currently on.
rangeIdx int
}
func newIPPool(ipset *netipx.IPSet) *ippool {
if ipset == nil {
return &ippool{}
}
return &ippool{ranges: ipset.Ranges()}
}
// next returns the next address from the set, or errPoolExhausted if we have
// iterated over the whole set.
func (ipp *ippool) next() (netip.Addr, error) {
if ipp.rangeIdx >= len(ipp.ranges) {
// ipset is empty or we have iterated off the end
return netip.Addr{}, errPoolExhausted
}
if !ipp.last.IsValid() {
// not initialized yet
ipp.last = ipp.ranges[0].From()
return ipp.last, nil
}
currRange := ipp.ranges[ipp.rangeIdx]
if ipp.last == currRange.To() {
// then we need to move to the next range
ipp.rangeIdx++
if ipp.rangeIdx >= len(ipp.ranges) {
return netip.Addr{}, errPoolExhausted
}
ipp.last = ipp.ranges[ipp.rangeIdx].From()
return ipp.last, nil
}
ipp.last = ipp.last.Next()
return ipp.last, nil
}
+1 -1
View File
@@ -44,7 +44,7 @@ var (
)
func replaceFileW(replaced *uint16, replacement *uint16, backup *uint16, flags uint32, exclude unsafe.Pointer, reserved unsafe.Pointer) (err error) {
r1, _, e1 := syscall.Syscall6(procReplaceFileW.Addr(), 6, uintptr(unsafe.Pointer(replaced)), uintptr(unsafe.Pointer(replacement)), uintptr(unsafe.Pointer(backup)), uintptr(flags), uintptr(exclude), uintptr(reserved))
r1, _, e1 := syscall.SyscallN(procReplaceFileW.Addr(), uintptr(unsafe.Pointer(replaced)), uintptr(unsafe.Pointer(replacement)), uintptr(unsafe.Pointer(backup)), uintptr(flags), uintptr(exclude), uintptr(reserved))
if int32(r1) == 0 {
err = errnoErr(e1)
}
+19 -1
View File
@@ -38,6 +38,7 @@ import (
"tailscale.com/net/udprelay/status"
"tailscale.com/paths"
"tailscale.com/safesocket"
"tailscale.com/syncs"
"tailscale.com/tailcfg"
"tailscale.com/types/appctype"
"tailscale.com/types/dnstype"
@@ -1363,7 +1364,7 @@ type IPNBusWatcher struct {
httpRes *http.Response
dec *json.Decoder
mu sync.Mutex
mu syncs.Mutex
closed bool
}
@@ -1400,6 +1401,23 @@ func (lc *Client) SuggestExitNode(ctx context.Context) (apitype.ExitNodeSuggesti
return decodeJSON[apitype.ExitNodeSuggestionResponse](body)
}
// CheckSOMarkInUse reports whether the socket mark option is in use. This will only
// be true if tailscale is running on Linux and tailscaled uses SO_MARK.
func (lc *Client) CheckSOMarkInUse(ctx context.Context) (bool, error) {
body, err := lc.get200(ctx, "/localapi/v0/check-so-mark-in-use")
if err != nil {
return false, err
}
var res struct {
UseSOMark bool `json:"useSoMark"`
}
if err := json.Unmarshal(body, &res); err != nil {
return false, fmt.Errorf("invalid JSON from check-so-mark-in-use: %w", err)
}
return res.UseSOMark, nil
}
// ShutdownTailscaled requests a graceful shutdown of tailscaled.
func (lc *Client) ShutdownTailscaled(ctx context.Context) error {
_, err := lc.send(ctx, "POST", "/localapi/v0/shutdown", 200, nil)
+2 -1
View File
@@ -18,6 +18,7 @@ import (
"golang.org/x/crypto/blake2s"
chp "golang.org/x/crypto/chacha20poly1305"
"tailscale.com/syncs"
"tailscale.com/types/key"
)
@@ -48,7 +49,7 @@ type Conn struct {
// rxState is all the Conn state that Read uses.
type rxState struct {
sync.Mutex
syncs.Mutex
cipher cipher.AEAD
nonce nonce
buf *maxMsgBuffer // or nil when reads exhausted
+69 -47
View File
@@ -23,6 +23,7 @@ import (
"tailscale.com/util/backoff"
"tailscale.com/util/clientmetric"
"tailscale.com/util/execqueue"
"tailscale.com/util/testenv"
)
type LoginGoal struct {
@@ -117,12 +118,13 @@ type Auto struct {
logf logger.Logf
closed bool
updateCh chan struct{} // readable when we should inform the server of a change
observer Observer // called to update Client status; always non-nil
observer Observer // if non-nil, called to update Client status
observerQueue execqueue.ExecQueue
shutdownFn func() // to be called prior to shutdown or nil
mu sync.Mutex // mutex guards the following fields
started bool // whether [Auto.Start] has been called
wantLoggedIn bool // whether the user wants to be logged in per last method call
urlToVisit string // the last url we were told to visit
expiry time.Time
@@ -138,7 +140,6 @@ type Auto struct {
loggedIn bool // true if currently logged in
loginGoal *LoginGoal // non-nil if some login activity is desired
inMapPoll bool // true once we get the first MapResponse in a stream; false when HTTP response ends
state State // TODO(bradfitz): delete this, make it computed by method from other state
authCtx context.Context // context used for auth requests
mapCtx context.Context // context used for netmap and update requests
@@ -151,15 +152,21 @@ type Auto struct {
// New creates and starts a new Auto.
func New(opts Options) (*Auto, error) {
c, err := NewNoStart(opts)
if c != nil {
c.Start()
c, err := newNoStart(opts)
if err != nil {
return nil, err
}
if opts.StartPaused {
c.SetPaused(true)
}
if !opts.SkipStartForTests {
c.start()
}
return c, err
}
// NewNoStart creates a new Auto, but without calling Start on it.
func NewNoStart(opts Options) (_ *Auto, err error) {
// newNoStart creates a new Auto, but without calling Start on it.
func newNoStart(opts Options) (_ *Auto, err error) {
direct, err := NewDirect(opts)
if err != nil {
return nil, err
@@ -170,9 +177,6 @@ func NewNoStart(opts Options) (_ *Auto, err error) {
}
}()
if opts.Observer == nil {
return nil, errors.New("missing required Options.Observer")
}
if opts.Logf == nil {
opts.Logf = func(fmt string, args ...any) {}
}
@@ -222,10 +226,21 @@ func (c *Auto) SetPaused(paused bool) {
c.unpauseWaiters = nil
}
// Start starts the client's goroutines.
// StartForTest starts the client's goroutines.
//
// It should only be called for clients created by NewNoStart.
func (c *Auto) Start() {
// It should only be called for clients created with [Options.SkipStartForTests].
func (c *Auto) StartForTest() {
testenv.AssertInTest()
c.start()
}
func (c *Auto) start() {
c.mu.Lock()
defer c.mu.Unlock()
if c.started {
return
}
c.started = true
go c.authRoutine()
go c.mapRoutine()
go c.updateRoutine()
@@ -299,10 +314,11 @@ func (c *Auto) authRoutine() {
c.mu.Lock()
goal := c.loginGoal
ctx := c.authCtx
loggedIn := c.loggedIn
if goal != nil {
c.logf("[v1] authRoutine: %s; wantLoggedIn=%v", c.state, true)
c.logf("[v1] authRoutine: loggedIn=%v; wantLoggedIn=%v", loggedIn, true)
} else {
c.logf("[v1] authRoutine: %s; goal=nil paused=%v", c.state, c.paused)
c.logf("[v1] authRoutine: loggedIn=%v; goal=nil paused=%v", loggedIn, c.paused)
}
c.mu.Unlock()
@@ -325,11 +341,6 @@ func (c *Auto) authRoutine() {
c.mu.Lock()
c.urlToVisit = goal.url
if goal.url != "" {
c.state = StateURLVisitRequired
} else {
c.state = StateAuthenticating
}
c.mu.Unlock()
var url string
@@ -363,7 +374,6 @@ func (c *Auto) authRoutine() {
flags: LoginDefault,
url: url,
}
c.state = StateURLVisitRequired
c.mu.Unlock()
c.sendStatus("authRoutine-url", err, url, nil)
@@ -383,7 +393,6 @@ func (c *Auto) authRoutine() {
c.urlToVisit = ""
c.loggedIn = true
c.loginGoal = nil
c.state = StateAuthenticated
c.mu.Unlock()
c.sendStatus("authRoutine-success", nil, "", nil)
@@ -433,21 +442,17 @@ func (mrs mapRoutineState) UpdateFullNetmap(nm *netmap.NetworkMap) {
c := mrs.c
c.mu.Lock()
ctx := c.mapCtx
c.inMapPoll = true
if c.loggedIn {
c.state = StateSynchronized
}
c.expiry = nm.Expiry
c.expiry = nm.SelfKeyExpiry()
stillAuthed := c.loggedIn
c.logf("[v1] mapRoutine: netmap received: %s", c.state)
c.logf("[v1] mapRoutine: netmap received: loggedIn=%v inMapPoll=true", stillAuthed)
c.mu.Unlock()
if stillAuthed {
c.sendStatus("mapRoutine-got-netmap", nil, "", nm)
}
// Reset the backoff timer if we got a netmap.
mrs.bo.BackOff(ctx, nil)
mrs.bo.Reset()
}
func (mrs mapRoutineState) UpdateNetmapDelta(muts []netmap.NodeMutation) bool {
@@ -488,8 +493,8 @@ func (c *Auto) mapRoutine() {
}
c.mu.Lock()
c.logf("[v1] mapRoutine: %s", c.state)
loggedIn := c.loggedIn
c.logf("[v1] mapRoutine: loggedIn=%v", loggedIn)
ctx := c.mapCtx
c.mu.Unlock()
@@ -520,9 +525,6 @@ func (c *Auto) mapRoutine() {
c.direct.health.SetOutOfPollNetMap()
c.mu.Lock()
c.inMapPoll = false
if c.state == StateSynchronized {
c.state = StateAuthenticated
}
paused := c.paused
c.mu.Unlock()
@@ -588,12 +590,12 @@ func (c *Auto) sendStatus(who string, err error, url string, nm *netmap.NetworkM
c.mu.Unlock()
return
}
state := c.state
loggedIn := c.loggedIn
inMapPoll := c.inMapPoll
loginGoal := c.loginGoal
c.mu.Unlock()
c.logf("[v1] sendStatus: %s: %v", who, state)
c.logf("[v1] sendStatus: %s: loggedIn=%v inMapPoll=%v", who, loggedIn, inMapPoll)
var p persist.PersistView
if nm != nil && loggedIn && inMapPoll {
@@ -604,18 +606,31 @@ func (c *Auto) sendStatus(who string, err error, url string, nm *netmap.NetworkM
nm = nil
}
newSt := &Status{
URL: url,
Persist: p,
NetMap: nm,
Err: err,
state: state,
URL: url,
Persist: p,
NetMap: nm,
Err: err,
LoggedIn: loggedIn && loginGoal == nil,
InMapPoll: inMapPoll,
}
if c.observer == nil {
return
}
c.lastStatus.Store(newSt)
// Launch a new goroutine to avoid blocking the caller while the observer
// does its thing, which may result in a call back into the client.
metricQueued.Add(1)
c.observerQueue.Add(func() {
c.mu.Lock()
closed := c.closed
c.mu.Unlock()
if closed {
return
}
if canSkipStatus(newSt, c.lastStatus.Load()) {
metricSkippable.Add(1)
if !c.direct.controlKnobs.DisableSkipStatusQueue.Load() {
@@ -659,14 +674,15 @@ func canSkipStatus(s1, s2 *Status) bool {
// we can't skip it.
return false
}
if s1.Err != nil || s1.URL != "" {
// If s1 has an error or a URL, we shouldn't skip it, lest the error go
// away in s2 or in-between. We want to make sure all the subsystems see
// it. Plus there aren't many of these, so not worth skipping.
if s1.Err != nil || s1.URL != "" || s1.LoggedIn {
// If s1 has an error, a URL, or LoginFinished set, we shouldn't skip it,
// lest the error go away in s2 or in-between. We want to make sure all
// the subsystems see it. Plus there aren't many of these, so not worth
// skipping.
return false
}
if !s1.Persist.Equals(s2.Persist) || s1.state != s2.state {
// If s1 has a different Persist or state than s2,
if !s1.Persist.Equals(s2.Persist) || s1.LoggedIn != s2.LoggedIn || s1.InMapPoll != s2.InMapPoll || s1.URL != s2.URL {
// If s1 has a different Persist, LoginFinished, Synced, or URL than s2,
// don't skip it. We only care about skipping the typical
// entries where the only difference is the NetMap.
return false
@@ -728,7 +744,6 @@ func (c *Auto) Logout(ctx context.Context) error {
}
c.mu.Lock()
c.loggedIn = false
c.state = StateNotAuthenticated
c.cancelAuthCtxLocked()
c.cancelMapCtxLocked()
c.mu.Unlock()
@@ -752,6 +767,13 @@ func (c *Auto) UpdateEndpoints(endpoints []tailcfg.Endpoint) {
}
}
// SetDiscoPublicKey sets the client's Disco public to key and sends the change
// to the control server.
func (c *Auto) SetDiscoPublicKey(key key.DiscoPublic) {
c.direct.SetDiscoPublicKey(key)
c.updateControl()
}
func (c *Auto) Shutdown() {
c.mu.Lock()
if c.closed {
+6
View File
@@ -12,6 +12,7 @@ import (
"context"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
)
// LoginFlags is a bitmask of options to change the behavior of Client.Login
@@ -80,7 +81,12 @@ type Client interface {
// TODO: a server-side change would let us simply upload this
// in a separate http request. It has nothing to do with the rest of
// the state machine.
// Note: the auto client uploads the new endpoints to control immediately.
UpdateEndpoints(endpoints []tailcfg.Endpoint)
// SetDiscoPublicKey updates the disco public key that will be sent in
// future map requests. This should be called after rotating the discovery key.
// Note: the auto client uploads the new key to control immediately.
SetDiscoPublicKey(key.DiscoPublic)
// ClientID returns the ClientID of a client. This ID is meant to
// distinguish one client from another.
ClientID() int64
+63 -7
View File
@@ -7,6 +7,8 @@ import (
"bytes"
"cmp"
"context"
"crypto"
"crypto/sha256"
"encoding/binary"
"encoding/json"
"errors"
@@ -21,7 +23,6 @@ import (
"runtime"
"slices"
"strings"
"sync"
"sync/atomic"
"time"
@@ -42,6 +43,7 @@ import (
"tailscale.com/net/netx"
"tailscale.com/net/tlsdial"
"tailscale.com/net/tsdial"
"tailscale.com/syncs"
"tailscale.com/tailcfg"
"tailscale.com/tka"
"tailscale.com/tstime"
@@ -72,7 +74,6 @@ type Direct struct {
logf logger.Logf
netMon *netmon.Monitor // non-nil
health *health.Tracker
discoPubKey key.DiscoPublic
busClient *eventbus.Client
clientVersionPub *eventbus.Publisher[tailcfg.ClientVersion]
autoUpdatePub *eventbus.Publisher[AutoUpdate]
@@ -90,9 +91,10 @@ type Direct struct {
dialPlan ControlDialPlanner // can be nil
mu sync.Mutex // mutex guards the following fields
mu syncs.Mutex // mutex guards the following fields
serverLegacyKey key.MachinePublic // original ("legacy") nacl crypto_box-based public key; only used for signRegisterRequest on Windows now
serverNoiseKey key.MachinePublic
discoPubKey key.DiscoPublic // protected by mu; can be updated via [SetDiscoPublicKey]
sfGroup singleflight.Group[struct{}, *ts2021.Client] // protects noiseClient creation.
noiseClient *ts2021.Client // also protected by mu
@@ -113,6 +115,9 @@ type Direct struct {
// Observer is implemented by users of the control client (such as LocalBackend)
// to get notified of changes in the control client's status.
//
// If an implementation of Observer also implements [NetmapDeltaUpdater], they get
// delta updates as well as full netmap updates.
type Observer interface {
// SetControlClientStatus is called when the client has a new status to
// report. The Client is provided to allow the Observer to track which
@@ -141,8 +146,17 @@ type Options struct {
ControlKnobs *controlknobs.Knobs // or nil to ignore
Bus *eventbus.Bus // non-nil, for setting up publishers
SkipStartForTests bool // if true, don't call [Auto.Start] to avoid any background goroutines (for tests only)
// StartPaused indicates whether the client should start in a paused state
// where it doesn't do network requests. This primarily exists for testing
// but not necessarily "go test" tests, so it isn't restricted to only
// being used in tests.
StartPaused bool
// Observer is called when there's a change in status to report
// from the control client.
// If nil, no status updates are reported.
Observer Observer
// SkipIPForwardingCheck declares that the host's IP
@@ -302,7 +316,6 @@ func NewDirect(opts Options) (*Direct, error) {
logf: opts.Logf,
persist: opts.Persist.View(),
authKey: opts.AuthKey,
discoPubKey: opts.DiscoPublicKey,
debugFlags: opts.DebugFlags,
netMon: netMon,
health: opts.HealthTracker,
@@ -315,6 +328,7 @@ func NewDirect(opts Options) (*Direct, error) {
dnsCache: dnsCache,
dialPlan: opts.DialPlan,
}
c.discoPubKey = opts.DiscoPublicKey
c.closedCtx, c.closeCtx = context.WithCancel(context.Background())
c.controlClientID = nextControlClientID.Add(1)
@@ -839,6 +853,14 @@ func (c *Direct) SendUpdate(ctx context.Context) error {
return c.sendMapRequest(ctx, false, nil)
}
// SetDiscoPublicKey updates the disco public key in local state.
// It does not implicitly trigger [SendUpdate]; callers should arrange for that.
func (c *Direct) SetDiscoPublicKey(key key.DiscoPublic) {
c.mu.Lock()
defer c.mu.Unlock()
c.discoPubKey = key
}
// ClientID returns the controlClientID of the controlClient.
func (c *Direct) ClientID() int64 {
return c.controlClientID
@@ -888,6 +910,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, isStreaming bool, nu Netmap
persist := c.persist
serverURL := c.serverURL
serverNoiseKey := c.serverNoiseKey
discoKey := c.discoPubKey
hi := c.hostInfoLocked()
backendLogID := hi.BackendLogID
connectionHandleForTest := c.connectionHandleForTest
@@ -931,11 +954,12 @@ func (c *Direct) sendMapRequest(ctx context.Context, isStreaming bool, nu Netmap
}
nodeKey := persist.PublicNodeKey()
request := &tailcfg.MapRequest{
Version: tailcfg.CurrentCapabilityVersion,
KeepAlive: true,
NodeKey: nodeKey,
DiscoKey: c.discoPubKey,
DiscoKey: discoKey,
Endpoints: eps,
EndpointTypes: epTypes,
Stream: isStreaming,
@@ -946,6 +970,26 @@ func (c *Direct) sendMapRequest(ctx context.Context, isStreaming bool, nu Netmap
ConnectionHandleForTest: connectionHandleForTest,
}
// If we have a hardware attestation key, sign the node key with it and send
// the key & signature in the map request.
if buildfeatures.HasTPM {
if k := persist.AsStruct().AttestationKey; k != nil && !k.IsZero() {
hwPub := key.HardwareAttestationPublicFromPlatformKey(k)
request.HardwareAttestationKey = hwPub
t := c.clock.Now()
msg := fmt.Sprintf("%d|%s", t.Unix(), nodeKey.String())
digest := sha256.Sum256([]byte(msg))
sig, err := k.Sign(nil, digest[:], crypto.SHA256)
if err != nil {
c.logf("failed to sign node key with hardware attestation key: %v", err)
} else {
request.HardwareAttestationKeySignature = sig
request.HardwareAttestationKeySignatureTimestamp = t
}
}
}
var extraDebugFlags []string
if buildfeatures.HasAdvertiseRoutes && hi != nil && c.netMon != nil && !c.skipIPForwardingCheck &&
ipForwardingBroken(hi.RoutableIPs, c.netMon.InterfaceState()) {
@@ -1059,7 +1103,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, isStreaming bool, nu Netmap
c.persist = newPersist.View()
persist = c.persist
}
c.expiry = nm.Expiry
c.expiry = nm.SelfKeyExpiry()
}
// gotNonKeepAliveMessage is whether we've yet received a MapResponse message without
@@ -1140,7 +1184,19 @@ func (c *Direct) sendMapRequest(ctx context.Context, isStreaming bool, nu Netmap
metricMapResponseKeepAlives.Add(1)
continue
}
if au, ok := resp.DefaultAutoUpdate.Get(); ok {
// DefaultAutoUpdate in its CapMap and deprecated top-level field forms.
if self := resp.Node; self != nil {
for _, v := range self.CapMap[tailcfg.NodeAttrDefaultAutoUpdate] {
switch v {
case "true", "false":
c.autoUpdatePub.Publish(AutoUpdate{c.controlClientID, v == "true"})
default:
c.logf("netmap: [unexpected] unknown %s in CapMap: %q", tailcfg.NodeAttrDefaultAutoUpdate, v)
}
}
}
if au, ok := resp.DeprecatedDefaultAutoUpdate.Get(); ok {
c.autoUpdatePub.Publish(AutoUpdate{c.controlClientID, au})
}
-3
View File
@@ -867,7 +867,6 @@ func (ms *mapSession) netmap() *netmap.NetworkMap {
nm := &netmap.NetworkMap{
NodeKey: ms.publicNodeKey,
PrivateKey: ms.privateNodeKey,
MachineKey: ms.machinePubKey,
Peers: peerViews,
UserProfiles: make(map[tailcfg.UserID]tailcfg.UserProfileView),
@@ -892,8 +891,6 @@ func (ms *mapSession) netmap() *netmap.NetworkMap {
if node := ms.lastNode; node.Valid() {
nm.SelfNode = node
nm.Expiry = node.KeyExpiry()
nm.Name = node.Name()
nm.AllCaps = ms.lastCapSet
}
+10 -80
View File
@@ -4,8 +4,6 @@
package controlclient
import (
"encoding/json"
"fmt"
"reflect"
"tailscale.com/types/netmap"
@@ -13,57 +11,6 @@ import (
"tailscale.com/types/structs"
)
// State is the high-level state of the client. It is used only in
// unit tests for proper sequencing, don't depend on it anywhere else.
//
// TODO(apenwarr): eliminate the state, as it's now obsolete.
//
// apenwarr: Historical note: controlclient.Auto was originally
// intended to be the state machine for the whole tailscale client, but that
// turned out to not be the right abstraction layer, and it moved to
// ipn.Backend. Since ipn.Backend now has a state machine, it would be
// much better if controlclient could be a simple stateless API. But the
// current server-side API (two interlocking polling https calls) makes that
// very hard to implement. A server side API change could untangle this and
// remove all the statefulness.
type State int
const (
StateNew = State(iota)
StateNotAuthenticated
StateAuthenticating
StateURLVisitRequired
StateAuthenticated
StateSynchronized // connected and received map update
)
func (s State) AppendText(b []byte) ([]byte, error) {
return append(b, s.String()...), nil
}
func (s State) MarshalText() ([]byte, error) {
return []byte(s.String()), nil
}
func (s State) String() string {
switch s {
case StateNew:
return "state:new"
case StateNotAuthenticated:
return "state:not-authenticated"
case StateAuthenticating:
return "state:authenticating"
case StateURLVisitRequired:
return "state:url-visit-required"
case StateAuthenticated:
return "state:authenticated"
case StateSynchronized:
return "state:synchronized"
default:
return fmt.Sprintf("state:unknown:%d", int(s))
}
}
type Status struct {
_ structs.Incomparable
@@ -76,6 +23,14 @@ type Status struct {
// URL, if non-empty, is the interactive URL to visit to finish logging in.
URL string
// LoggedIn, if true, indicates that serveRegister has completed and no
// other login change is in progress.
LoggedIn bool
// InMapPoll, if true, indicates that we've received at least one netmap
// and are connected to receive updates.
InMapPoll bool
// NetMap is the latest server-pushed state of the tailnet network.
NetMap *netmap.NetworkMap
@@ -83,26 +38,8 @@ type Status struct {
//
// TODO(bradfitz,maisem): clarify this.
Persist persist.PersistView
// state is the internal state. It should not be exposed outside this
// package, but we have some automated tests elsewhere that need to
// use it via the StateForTest accessor.
// TODO(apenwarr): Unexport or remove these.
state State
}
// LoginFinished reports whether the controlclient is in its "StateAuthenticated"
// state where it's in a happy register state but not yet in a map poll.
//
// TODO(bradfitz): delete this and everything around Status.state.
func (s *Status) LoginFinished() bool { return s.state == StateAuthenticated }
// StateForTest returns the internal state of s for tests only.
func (s *Status) StateForTest() State { return s.state }
// SetStateForTest sets the internal state of s for tests only.
func (s *Status) SetStateForTest(state State) { s.state = state }
// Equal reports whether s and s2 are equal.
func (s *Status) Equal(s2 *Status) bool {
if s == nil && s2 == nil {
@@ -111,15 +48,8 @@ func (s *Status) Equal(s2 *Status) bool {
return s != nil && s2 != nil &&
s.Err == s2.Err &&
s.URL == s2.URL &&
s.state == s2.state &&
s.LoggedIn == s2.LoggedIn &&
s.InMapPoll == s2.InMapPoll &&
reflect.DeepEqual(s.Persist, s2.Persist) &&
reflect.DeepEqual(s.NetMap, s2.NetMap)
}
func (s Status) String() string {
b, err := json.MarshalIndent(s, "", "\t")
if err != nil {
panic(err)
}
return s.state.String() + " " + string(b)
}
+2 -2
View File
@@ -28,19 +28,19 @@ import (
"slices"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"tailscale.com/feature/buildfeatures"
"tailscale.com/kube/kubetypes"
"tailscale.com/syncs"
"tailscale.com/types/opt"
"tailscale.com/version"
"tailscale.com/version/distro"
)
var (
mu sync.Mutex
mu syncs.Mutex
// +checklocks:mu
set = map[string]string{}
// +checklocks:mu
@@ -0,0 +1,13 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Code generated by gen.go; DO NOT EDIT.
//go:build ts_omit_cachenetmap
package buildfeatures
// HasCacheNetMap is whether the binary was built with support for modular feature "Cache the netmap on disk between runs".
// Specifically, it's whether the binary was NOT built with the "ts_omit_cachenetmap" build tag.
// It's a const so it can be used for dead code elimination.
const HasCacheNetMap = false
@@ -0,0 +1,13 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Code generated by gen.go; DO NOT EDIT.
//go:build !ts_omit_cachenetmap
package buildfeatures
// HasCacheNetMap is whether the binary was built with support for modular feature "Cache the netmap on disk between runs".
// Specifically, it's whether the binary was NOT built with the "ts_omit_cachenetmap" build tag.
// It's a const so it can be used for dead code elimination.
const HasCacheNetMap = true
@@ -3,11 +3,11 @@
// Code generated by gen.go; DO NOT EDIT.
//go:build ts_omit_identity_federation
//go:build ts_omit_identityfederation
package buildfeatures
// HasIdentityFederation is whether the binary was built with support for modular feature "Identity token exchange for auth key support".
// Specifically, it's whether the binary was NOT built with the "ts_omit_identity_federation" build tag.
// HasIdentityFederation is whether the binary was built with support for modular feature "Auth key generation via identity federation support".
// Specifically, it's whether the binary was NOT built with the "ts_omit_identityfederation" build tag.
// It's a const so it can be used for dead code elimination.
const HasIdentityFederation = false
@@ -3,11 +3,11 @@
// Code generated by gen.go; DO NOT EDIT.
//go:build !ts_omit_identity_federation
//go:build !ts_omit_identityfederation
package buildfeatures
// HasIdentityFederation is whether the binary was built with support for modular feature "Identity token exchange for auth key support".
// Specifically, it's whether the binary was NOT built with the "ts_omit_identity_federation" build tag.
// HasIdentityFederation is whether the binary was built with support for modular feature "Auth key generation via identity federation support".
// Specifically, it's whether the binary was NOT built with the "ts_omit_identityfederation" build tag.
// It's a const so it can be used for dead code elimination.
const HasIdentityFederation = true
+15
View File
@@ -7,6 +7,8 @@ package feature
import (
"errors"
"reflect"
"tailscale.com/util/testenv"
)
var ErrUnavailable = errors.New("feature not included in this build")
@@ -55,6 +57,19 @@ func (h *Hook[Func]) Set(f Func) {
h.ok = true
}
// SetForTest sets the hook function for tests, blowing
// away any previous value. It will panic if called from
// non-test code.
//
// It returns a restore function that resets the hook
// to its previous value.
func (h *Hook[Func]) SetForTest(f Func) (restore func()) {
testenv.AssertInTest()
old := *h
h.f, h.ok = f, true
return func() { *h = old }
}
// Get returns the hook function, or panics if it hasn't been set.
// Use IsSet to check if it's been set, or use GetOrNil if you're
// okay with a nil return value.
+9
View File
@@ -6,6 +6,8 @@ package feature
import (
"net/http"
"net/url"
"os"
"sync"
"tailscale.com/types/logger"
"tailscale.com/types/persist"
@@ -15,9 +17,16 @@ import (
// to conditionally initialize.
var HookCanAutoUpdate Hook[func() bool]
var testAllowAutoUpdate = sync.OnceValue(func() bool {
return os.Getenv("TS_TEST_ALLOW_AUTO_UPDATE") == "1"
})
// CanAutoUpdate reports whether the current binary is built with auto-update
// support and, if so, whether the current platform supports it.
func CanAutoUpdate() bool {
if testAllowAutoUpdate() {
return true
}
if f, ok := HookCanAutoUpdate.GetOk(); ok {
return f()
}
+8 -1
View File
@@ -23,10 +23,17 @@ var HookSystemdStatus Hook[func(format string, args ...any)]
// It does nothing on non-Linux systems or if the binary was built without
// the sdnotify feature.
func SystemdStatus(format string, args ...any) {
if runtime.GOOS != "linux" || !buildfeatures.HasSDNotify {
if !CanSystemdStatus { // mid-stack inlining DCE
return
}
if f, ok := HookSystemdStatus.GetOk(); ok {
f(format, args...)
}
}
// CanSystemdStatus reports whether the current build has systemd notifications
// linked in.
//
// It's effectively the same as HookSystemdStatus.IsSet(), but a constant for
// dead code elimination reasons.
const CanSystemdStatus = runtime.GOOS == "linux" && buildfeatures.HasSDNotify
+1 -2
View File
@@ -151,5 +151,4 @@
});
};
}
# nix-direnv cache busting line: sha256-AUOjLomba75qfzb9Vxc0Sktyeces6hBSuOMgboWcDnE=
# nix-direnv cache busting line: sha256-jJSSXMyUqcJoZuqfSlBsKDQezyqS+jDkRglMMjG1K8g=
+1 -1
View File
@@ -1 +1 @@
sha256-AUOjLomba75qfzb9Vxc0Sktyeces6hBSuOMgboWcDnE=
sha256-jJSSXMyUqcJoZuqfSlBsKDQezyqS+jDkRglMMjG1K8g=
+1 -1
View File
@@ -1 +1 @@
5c01b77ad0d27a8bd4ef89ef7e713fd7043c5a91
0bab982699fa5903259ba9b4cba3e5fd6cb3baf2
+1 -1
View File
@@ -1 +1 @@
sha256-2TYziJLJrFOW2FehhahKficnDACJEwjuvVYyeQZbrcc=
sha256-fBezkBGRHCnfJiOUmMMqBCPCqjlGC4F6KEt5h1JhsCg=
+1 -1
View File
@@ -1 +1 @@
1.25.3
1.25.5
+2 -1
View File
@@ -20,6 +20,7 @@ import (
"tailscale.com/envknob"
"tailscale.com/feature/buildfeatures"
"tailscale.com/syncs"
"tailscale.com/tailcfg"
"tailscale.com/tstime"
"tailscale.com/types/opt"
@@ -30,7 +31,7 @@ import (
)
var (
mu sync.Mutex
mu syncs.Mutex
debugHandler map[string]http.Handler
)
+6 -5
View File
@@ -8,9 +8,10 @@
package healthmsg
const (
WarnAcceptRoutesOff = "Some peers are advertising routes but --accept-routes is false"
TailscaleSSHOnBut = "Tailscale SSH enabled, but " // + ... something from caller
LockedOut = "this node is locked out; it will not have connectivity until it is signed. For more info, see https://tailscale.com/s/locked-out"
WarnExitNodeUsage = "The following issues on your machine will likely make usage of exit nodes impossible"
DisableRPFilter = "Please set rp_filter=2 instead of rp_filter=1; see https://github.com/tailscale/tailscale/issues/3310"
WarnAcceptRoutesOff = "Some peers are advertising routes but --accept-routes is false"
TailscaleSSHOnBut = "Tailscale SSH enabled, but " // + ... something from caller
LockedOut = "this node is locked out; it will not have connectivity until it is signed. For more info, see https://tailscale.com/s/locked-out"
WarnExitNodeUsage = "The following issues on your machine will likely make usage of exit nodes impossible"
DisableRPFilter = "Please set rp_filter=2 instead of rp_filter=1; see https://github.com/tailscale/tailscale/issues/3310"
InMemoryTailnetLockState = "Tailnet Lock state is only being stored in-memory. Set --statedir to store state on disk, which is more secure. See https://tailscale.com/kb/1226/tailnet-lock#tailnet-lock-state"
)
+1 -1
View File
@@ -74,7 +74,7 @@ const (
NotifyInitialPrefs NotifyWatchOpt = 1 << 2 // if set, the first Notify message (sent immediately) will contain the current Prefs
NotifyInitialNetMap NotifyWatchOpt = 1 << 3 // if set, the first Notify message (sent immediately) will contain the current NetMap
NotifyNoPrivateKeys NotifyWatchOpt = 1 << 4 // if set, private keys that would normally be sent in updates are zeroed out
NotifyNoPrivateKeys NotifyWatchOpt = 1 << 4 // (no-op) it used to redact private keys; now they always are and this does nothing
NotifyInitialDriveShares NotifyWatchOpt = 1 << 5 // if set, the first Notify message (sent immediately) will contain the current Taildrive Shares
NotifyInitialOutgoingFiles NotifyWatchOpt = 1 << 6 // if set, the first Notify message (sent immediately) will contain the current Taildrop OutgoingFiles
+48 -41
View File
@@ -64,45 +64,48 @@ func (src *Prefs) Clone() *Prefs {
if dst.RelayServerPort != nil {
dst.RelayServerPort = ptr.To(*src.RelayServerPort)
}
dst.RelayServerStaticEndpoints = append(src.RelayServerStaticEndpoints[:0:0], src.RelayServerStaticEndpoints...)
dst.Persist = src.Persist.Clone()
return dst
}
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
var _PrefsCloneNeedsRegeneration = Prefs(struct {
ControlURL string
RouteAll bool
ExitNodeID tailcfg.StableNodeID
ExitNodeIP netip.Addr
AutoExitNode ExitNodeExpression
InternalExitNodePrior tailcfg.StableNodeID
ExitNodeAllowLANAccess bool
CorpDNS bool
RunSSH bool
RunWebClient bool
WantRunning bool
LoggedOut bool
ShieldsUp bool
AdvertiseTags []string
Hostname string
NotepadURLs bool
ForceDaemon bool
Egg bool
AdvertiseRoutes []netip.Prefix
AdvertiseServices []string
NoSNAT bool
NoStatefulFiltering opt.Bool
NetfilterMode preftype.NetfilterMode
OperatorUser string
ProfileName string
AutoUpdate AutoUpdatePrefs
AppConnector AppConnectorPrefs
PostureChecking bool
NetfilterKind string
DriveShares []*drive.Share
RelayServerPort *int
AllowSingleHosts marshalAsTrueInJSON
Persist *persist.Persist
ControlURL string
RouteAll bool
ExitNodeID tailcfg.StableNodeID
ExitNodeIP netip.Addr
AutoExitNode ExitNodeExpression
InternalExitNodePrior tailcfg.StableNodeID
ExitNodeAllowLANAccess bool
CorpDNS bool
RunSSH bool
RunWebClient bool
WantRunning bool
LoggedOut bool
ShieldsUp bool
AdvertiseTags []string
Hostname string
NotepadURLs bool
ForceDaemon bool
Egg bool
AdvertiseRoutes []netip.Prefix
AdvertiseServices []string
Sync opt.Bool
NoSNAT bool
NoStatefulFiltering opt.Bool
NetfilterMode preftype.NetfilterMode
OperatorUser string
ProfileName string
AutoUpdate AutoUpdatePrefs
AppConnector AppConnectorPrefs
PostureChecking bool
NetfilterKind string
DriveShares []*drive.Share
RelayServerPort *uint16
RelayServerStaticEndpoints []netip.AddrPort
AllowSingleHosts marshalAsTrueInJSON
Persist *persist.Persist
}{})
// Clone makes a deep copy of ServeConfig.
@@ -218,10 +221,11 @@ func (src *TCPPortHandler) Clone() *TCPPortHandler {
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
var _TCPPortHandlerCloneNeedsRegeneration = TCPPortHandler(struct {
HTTPS bool
HTTP bool
TCPForward string
TerminateTLS string
HTTPS bool
HTTP bool
TCPForward string
TerminateTLS string
ProxyProtocol int
}{})
// Clone makes a deep copy of HTTPHandler.
@@ -232,14 +236,17 @@ func (src *HTTPHandler) Clone() *HTTPHandler {
}
dst := new(HTTPHandler)
*dst = *src
dst.AcceptAppCaps = append(src.AcceptAppCaps[:0:0], src.AcceptAppCaps...)
return dst
}
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
var _HTTPHandlerCloneNeedsRegeneration = HTTPHandler(struct {
Path string
Proxy string
Text string
Path string
Proxy string
Text string
AcceptAppCaps []tailcfg.PeerCapability
Redirect string
}{})
// Clone makes a deep copy of WebServerConfig.
@@ -256,7 +263,7 @@ func (src *WebServerConfig) Clone() *WebServerConfig {
if v == nil {
dst.Handlers[k] = nil
} else {
dst.Handlers[k] = ptr.To(*v)
dst.Handlers[k] = v.Clone()
}
}
}
+81 -45
View File
@@ -363,6 +363,12 @@ func (v PrefsView) AdvertiseServices() views.Slice[string] {
return views.SliceOf(v.ж.AdvertiseServices)
}
// Sync is whether this node should sync its configuration from
// the control plane. If unset, this defaults to true.
// This exists primarily for testing, to verify that netmap caching
// and offline operation work correctly.
func (v PrefsView) Sync() opt.Bool { return v.ж.Sync }
// NoSNAT specifies whether to source NAT traffic going to
// destinations in AdvertiseRoutes. The default is to apply source
// NAT, which makes the traffic appear to come from the router
@@ -435,16 +441,21 @@ func (v PrefsView) DriveShares() views.SliceView[*drive.Share, drive.ShareView]
// RelayServerPort is the UDP port number for the relay server to bind to,
// on all interfaces. A non-nil zero value signifies a random unused port
// should be used. A nil value signifies relay server functionality
// should be disabled. This field is currently experimental, and therefore
// no guarantees are made about its current naming and functionality when
// non-nil/enabled.
func (v PrefsView) RelayServerPort() views.ValuePointer[int] {
// should be disabled.
func (v PrefsView) RelayServerPort() views.ValuePointer[uint16] {
return views.ValuePointerOf(v.ж.RelayServerPort)
}
// RelayServerStaticEndpoints are static IP:port endpoints to advertise as
// candidates for relay connections. Only relevant when RelayServerPort is
// non-nil.
func (v PrefsView) RelayServerStaticEndpoints() views.Slice[netip.AddrPort] {
return views.SliceOf(v.ж.RelayServerStaticEndpoints)
}
// AllowSingleHosts was a legacy field that was always true
// for the past 4.5 years. It controlled whether Tailscale
// peers got /32 or /127 routes for each other.
// peers got /32 or /128 routes for each other.
// As of 2024-05-17 we're starting to ignore it, but to let
// people still downgrade Tailscale versions and not break
// all peer-to-peer networking we still write it to disk (as JSON)
@@ -462,39 +473,41 @@ func (v PrefsView) Persist() persist.PersistView { return v.ж.Persist.View() }
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
var _PrefsViewNeedsRegeneration = Prefs(struct {
ControlURL string
RouteAll bool
ExitNodeID tailcfg.StableNodeID
ExitNodeIP netip.Addr
AutoExitNode ExitNodeExpression
InternalExitNodePrior tailcfg.StableNodeID
ExitNodeAllowLANAccess bool
CorpDNS bool
RunSSH bool
RunWebClient bool
WantRunning bool
LoggedOut bool
ShieldsUp bool
AdvertiseTags []string
Hostname string
NotepadURLs bool
ForceDaemon bool
Egg bool
AdvertiseRoutes []netip.Prefix
AdvertiseServices []string
NoSNAT bool
NoStatefulFiltering opt.Bool
NetfilterMode preftype.NetfilterMode
OperatorUser string
ProfileName string
AutoUpdate AutoUpdatePrefs
AppConnector AppConnectorPrefs
PostureChecking bool
NetfilterKind string
DriveShares []*drive.Share
RelayServerPort *int
AllowSingleHosts marshalAsTrueInJSON
Persist *persist.Persist
ControlURL string
RouteAll bool
ExitNodeID tailcfg.StableNodeID
ExitNodeIP netip.Addr
AutoExitNode ExitNodeExpression
InternalExitNodePrior tailcfg.StableNodeID
ExitNodeAllowLANAccess bool
CorpDNS bool
RunSSH bool
RunWebClient bool
WantRunning bool
LoggedOut bool
ShieldsUp bool
AdvertiseTags []string
Hostname string
NotepadURLs bool
ForceDaemon bool
Egg bool
AdvertiseRoutes []netip.Prefix
AdvertiseServices []string
Sync opt.Bool
NoSNAT bool
NoStatefulFiltering opt.Bool
NetfilterMode preftype.NetfilterMode
OperatorUser string
ProfileName string
AutoUpdate AutoUpdatePrefs
AppConnector AppConnectorPrefs
PostureChecking bool
NetfilterKind string
DriveShares []*drive.Share
RelayServerPort *uint16
RelayServerStaticEndpoints []netip.AddrPort
AllowSingleHosts marshalAsTrueInJSON
Persist *persist.Persist
}{})
// View returns a read-only view of ServeConfig.
@@ -807,12 +820,19 @@ func (v TCPPortHandlerView) TCPForward() string { return v.ж.TCPForward }
// (the HTTPS mode uses ServeConfig.Web)
func (v TCPPortHandlerView) TerminateTLS() string { return v.ж.TerminateTLS }
// ProxyProtocol indicates whether to send a PROXY protocol header
// before forwarding the connection to TCPForward.
//
// This is only valid if TCPForward is non-empty.
func (v TCPPortHandlerView) ProxyProtocol() int { return v.ж.ProxyProtocol }
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
var _TCPPortHandlerViewNeedsRegeneration = TCPPortHandler(struct {
HTTPS bool
HTTP bool
TCPForward string
TerminateTLS string
HTTPS bool
HTTP bool
TCPForward string
TerminateTLS string
ProxyProtocol int
}{})
// View returns a read-only view of HTTPHandler.
@@ -891,11 +911,27 @@ func (v HTTPHandlerView) Proxy() string { return v.ж.Proxy }
// plaintext to serve (primarily for testing)
func (v HTTPHandlerView) Text() string { return v.ж.Text }
// peer capabilities to forward in grant header, e.g. example.com/cap/mon
func (v HTTPHandlerView) AcceptAppCaps() views.Slice[tailcfg.PeerCapability] {
return views.SliceOf(v.ж.AcceptAppCaps)
}
// Redirect, if not empty, is the target URL to redirect requests to.
// By default, we redirect with HTTP 302 (Found) status.
// If Redirect starts with '<httpcode>:', then we use that status instead.
//
// The target URL supports the following expansion variables:
// - ${HOST}: replaced with the request's Host header value
// - ${REQUEST_URI}: replaced with the request's full URI (path and query string)
func (v HTTPHandlerView) Redirect() string { return v.ж.Redirect }
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
var _HTTPHandlerViewNeedsRegeneration = HTTPHandler(struct {
Path string
Proxy string
Text string
Path string
Proxy string
Text string
AcceptAppCaps []tailcfg.PeerCapability
Redirect string
}{})
// View returns a read-only view of WebServerConfig.
+2 -1
View File
@@ -323,7 +323,8 @@ type ProfileStateChangeCallback func(_ ipn.LoginProfileView, _ ipn.PrefsView, sa
// [ProfileStateChangeCallback]s are called first.
//
// It returns a function to be called when the cc is being shut down,
// or nil if no cleanup is needed.
// or nil if no cleanup is needed. That cleanup function should not call
// back into LocalBackend, which may be locked during shutdown.
type NewControlClientCallback func(controlclient.Client, ipn.LoginProfileView) (cleanup func())
// Hooks is a collection of hooks that extensions can add to (non-concurrently)
-1
View File
@@ -179,7 +179,6 @@ func handleC2NDebugNetMap(b *LocalBackend, w http.ResponseWriter, r *http.Reques
}
field.SetZero()
}
nm, _ = redactNetmapPrivateKeys(nm)
return json.Marshal(nm)
}
+12 -5
View File
@@ -30,7 +30,6 @@ import (
"runtime"
"slices"
"strings"
"sync"
"time"
"tailscale.com/atomicfile"
@@ -42,6 +41,7 @@ import (
"tailscale.com/ipn/store"
"tailscale.com/ipn/store/mem"
"tailscale.com/net/bakedroots"
"tailscale.com/syncs"
"tailscale.com/tailcfg"
"tailscale.com/tempfork/acme"
"tailscale.com/types/logger"
@@ -60,9 +60,9 @@ var (
// acmeMu guards all ACME operations, so concurrent requests
// for certs don't slam ACME. The first will go through and
// populate the on-disk cache and the rest should use that.
acmeMu sync.Mutex
acmeMu syncs.Mutex
renewMu sync.Mutex // lock order: acmeMu before renewMu
renewMu syncs.Mutex // lock order: acmeMu before renewMu
renewCertAt = map[string]time.Time{}
)
@@ -144,7 +144,11 @@ func (b *LocalBackend) GetCertPEMWithValidity(ctx context.Context, domain string
if minValidity == 0 {
logf("starting async renewal")
// Start renewal in the background, return current valid cert.
b.goTracker.Go(func() { getCertPEM(context.Background(), b, cs, logf, traceACME, domain, now, minValidity) })
b.goTracker.Go(func() {
if _, err := getCertPEM(context.Background(), b, cs, logf, traceACME, domain, now, minValidity); err != nil {
logf("async renewal failed: getCertPem: %v", err)
}
})
return pair, nil
}
// If the caller requested a specific validity duration, fall through
@@ -547,8 +551,11 @@ var getCertPEM = func(ctx context.Context, b *LocalBackend, cs certStore, logf l
// If we have a previous cert, include it in the order. Assuming we're
// within the ARI renewal window this should exclude us from LE rate
// limits.
// Note that this order extension will fail renewals if the ACME account key has changed
// since the last issuance, see
// https://github.com/tailscale/tailscale/issues/18251
var opts []acme.OrderOption
if previous != nil {
if previous != nil && !envknob.Bool("TS_DEBUG_ACME_FORCE_RENEWAL") {
prevCrt, err := previous.parseCertificate()
if err == nil {
opts = append(opts, acme.WithOrderReplacesCert(prevCrt))
+39 -36
View File
@@ -433,7 +433,7 @@ func (rbw *responseBodyWrapper) Close() error {
// b.Dialer().PeerAPITransport() with metrics tracking.
type driveTransport struct {
b *LocalBackend
tr *http.Transport
tr http.RoundTripper
}
func (b *LocalBackend) newDriveTransport() *driveTransport {
@@ -443,7 +443,7 @@ func (b *LocalBackend) newDriveTransport() *driveTransport {
}
}
func (dt *driveTransport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
func (dt *driveTransport) RoundTrip(req *http.Request) (*http.Response, error) {
// Some WebDAV clients include origin and refer headers, which peerapi does
// not like. Remove them.
req.Header.Del("origin")
@@ -455,42 +455,45 @@ func (dt *driveTransport) RoundTrip(req *http.Request) (resp *http.Response, err
req.Body = bw
}
defer func() {
contentType := "unknown"
if ct := req.Header.Get("Content-Type"); ct != "" {
contentType = ct
}
resp, err := dt.tr.RoundTrip(req)
if err != nil {
return nil, err
}
dt.b.mu.Lock()
selfNodeKey := dt.b.currentNode().Self().Key().ShortString()
dt.b.mu.Unlock()
n, _, ok := dt.b.WhoIs("tcp", netip.MustParseAddrPort(req.URL.Host))
shareNodeKey := "unknown"
if ok {
shareNodeKey = string(n.Key().ShortString())
}
contentType := "unknown"
if ct := req.Header.Get("Content-Type"); ct != "" {
contentType = ct
}
rbw := responseBodyWrapper{
log: dt.b.logf,
logVerbose: req.Method != httpm.GET && req.Method != httpm.PUT, // other requests like PROPFIND are quite chatty, so we log those at verbose level
method: req.Method,
bytesTx: int64(bw.bytesRead),
selfNodeKey: selfNodeKey,
shareNodeKey: shareNodeKey,
contentType: contentType,
contentLength: resp.ContentLength,
fileExtension: parseDriveFileExtensionForLog(req.URL.Path),
statusCode: resp.StatusCode,
ReadCloser: resp.Body,
}
dt.b.mu.Lock()
selfNodeKey := dt.b.currentNode().Self().Key().ShortString()
dt.b.mu.Unlock()
n, _, ok := dt.b.WhoIs("tcp", netip.MustParseAddrPort(req.URL.Host))
shareNodeKey := "unknown"
if ok {
shareNodeKey = string(n.Key().ShortString())
}
if resp.StatusCode >= 400 {
// in case of error response, just log immediately
rbw.logAccess("")
} else {
resp.Body = &rbw
}
}()
rbw := responseBodyWrapper{
log: dt.b.logf,
logVerbose: req.Method != httpm.GET && req.Method != httpm.PUT, // other requests like PROPFIND are quite chatty, so we log those at verbose level
method: req.Method,
bytesTx: int64(bw.bytesRead),
selfNodeKey: selfNodeKey,
shareNodeKey: shareNodeKey,
contentType: contentType,
contentLength: resp.ContentLength,
fileExtension: parseDriveFileExtensionForLog(req.URL.Path),
statusCode: resp.StatusCode,
ReadCloser: resp.Body,
}
return dt.tr.RoundTrip(req)
if resp.StatusCode >= 400 {
// in case of error response, just log immediately
rbw.logAccess("")
} else {
resp.Body = &rbw
}
return resp, nil
}
+48
View File
@@ -0,0 +1,48 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build !ts_omit_tpm
package ipnlocal
import (
"errors"
"tailscale.com/feature"
"tailscale.com/types/key"
"tailscale.com/types/logger"
"tailscale.com/types/persist"
)
func init() {
feature.HookGenerateAttestationKeyIfEmpty.Set(generateAttestationKeyIfEmpty)
}
// generateAttestationKeyIfEmpty generates a new hardware attestation key if
// none exists. It returns true if a new key was generated and stored in
// p.AttestationKey.
func generateAttestationKeyIfEmpty(p *persist.Persist, logf logger.Logf) (bool, error) {
// attempt to generate a new hardware attestation key if none exists
var ak key.HardwareAttestationKey
if p != nil {
ak = p.AttestationKey
}
if ak == nil || ak.IsZero() {
var err error
ak, err = key.NewHardwareAttestationKey()
if err != nil {
if !errors.Is(err, key.ErrUnsupported) {
logf("failed to create hardware attestation key: %v", err)
}
} else if ak != nil {
logf("using new hardware attestation key: %v", ak.Public())
if p == nil {
p = &persist.Persist{}
}
p.AttestationKey = ak
return true, nil
}
}
return false, nil
}
+493 -529
View File
File diff suppressed because it is too large. Load diff
+48 -83
View File
@@ -23,6 +23,7 @@ import (
"slices"
"time"
"tailscale.com/health"
"tailscale.com/health/healthmsg"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnstate"
@@ -54,7 +55,7 @@ var (
type tkaState struct {
profile ipn.ProfileID
authority *tka.Authority
storage *tka.FS
storage tka.CompactableChonk
filtered []ipnstate.TKAPeer
}
@@ -75,7 +76,7 @@ func (b *LocalBackend) initTKALocked() error {
root := b.TailscaleVarRoot()
if root == "" {
b.tka = nil
b.logf("network-lock unavailable; no state directory")
b.logf("cannot fetch existing TKA state; no state directory for network-lock")
return nil
}
@@ -90,6 +91,7 @@ func (b *LocalBackend) initTKALocked() error {
if err != nil {
return fmt.Errorf("initializing tka: %v", err)
}
if err := authority.Compact(storage, tkaCompactionDefaults); err != nil {
b.logf("tka compaction failed: %v", err)
}
@@ -105,6 +107,16 @@ func (b *LocalBackend) initTKALocked() error {
return nil
}
// noNetworkLockStateDirWarnable is a Warnable to warn the user that Tailnet Lock data
// (in particular, the list of AUMs in the TKA state) is being stored in memory and will
// be lost when tailscaled restarts.
var noNetworkLockStateDirWarnable = health.Register(&health.Warnable{
Code: "no-tailnet-lock-state-dir",
Title: "No statedir for Tailnet Lock",
Severity: health.SeverityMedium,
Text: health.StaticMessage(healthmsg.InMemoryTailnetLockState),
})
// tkaFilterNetmapLocked checks the signatures on each node key, dropping
// nodes from the netmap whose signature does not verify.
//
@@ -288,8 +300,11 @@ func (b *LocalBackend) tkaSyncIfNeeded(nm *netmap.NetworkMap, prefs ipn.PrefsVie
return nil
}
if b.tka != nil || nm.TKAEnabled {
b.logf("tkaSyncIfNeeded: enabled=%v, head=%v", nm.TKAEnabled, nm.TKAHead)
isEnabled := b.tka != nil
wantEnabled := nm.TKAEnabled
if isEnabled || wantEnabled {
b.logf("tkaSyncIfNeeded: isEnabled=%t, wantEnabled=%t, head=%v", isEnabled, wantEnabled, nm.TKAHead)
}
ourNodeKey, ok := prefs.Persist().PublicNodeKeyOK()
@@ -297,8 +312,6 @@ func (b *LocalBackend) tkaSyncIfNeeded(nm *netmap.NetworkMap, prefs ipn.PrefsVie
return errors.New("tkaSyncIfNeeded: no node key in prefs")
}
isEnabled := b.tka != nil
wantEnabled := nm.TKAEnabled
didJustEnable := false
if isEnabled != wantEnabled {
var ourHead tka.AUMHash
@@ -343,25 +356,18 @@ func (b *LocalBackend) tkaSyncIfNeeded(nm *netmap.NetworkMap, prefs ipn.PrefsVie
if err := b.tkaSyncLocked(ourNodeKey); err != nil {
return fmt.Errorf("tka sync: %w", err)
}
// Try to compact the TKA state, to avoid unbounded storage on nodes.
//
// We run this on every sync so that clients compact consistently. In many
// cases this will be a no-op.
if err := b.tka.authority.Compact(b.tka.storage, tkaCompactionDefaults); err != nil {
return fmt.Errorf("tka compact: %w", err)
}
}
return nil
}
func toSyncOffer(head string, ancestors []string) (tka.SyncOffer, error) {
var out tka.SyncOffer
if err := out.Head.UnmarshalText([]byte(head)); err != nil {
return tka.SyncOffer{}, fmt.Errorf("head.UnmarshalText: %v", err)
}
out.Ancestors = make([]tka.AUMHash, len(ancestors))
for i, a := range ancestors {
if err := out.Ancestors[i].UnmarshalText([]byte(a)); err != nil {
return tka.SyncOffer{}, fmt.Errorf("ancestor[%d].UnmarshalText: %v", i, err)
}
}
return out, nil
}
// tkaSyncLocked synchronizes TKA state with control. b.mu must be held
// and tka must be initialized. b.mu will be stepped out of (and back into)
// during network RPCs.
@@ -379,7 +385,7 @@ func (b *LocalBackend) tkaSyncLocked(ourNodeKey key.NodePublic) error {
if err != nil {
return fmt.Errorf("offer RPC: %w", err)
}
controlOffer, err := toSyncOffer(offerResp.Head, offerResp.Ancestors)
controlOffer, err := tka.ToSyncOffer(offerResp.Head, offerResp.Ancestors)
if err != nil {
return fmt.Errorf("control offer: %v", err)
}
@@ -442,7 +448,7 @@ func (b *LocalBackend) tkaSyncLocked(ourNodeKey key.NodePublic) error {
// b.mu must be held & TKA must be initialized.
func (b *LocalBackend) tkaApplyDisablementLocked(secret []byte) error {
if b.tka.authority.ValidDisablement(secret) {
if err := os.RemoveAll(b.chonkPathLocked()); err != nil {
if err := b.tka.storage.RemoveAll(); err != nil {
return err
}
b.tka = nil
@@ -464,10 +470,6 @@ func (b *LocalBackend) chonkPathLocked() string {
//
// b.mu must be held.
func (b *LocalBackend) tkaBootstrapFromGenesisLocked(g tkatype.MarshaledAUM, persist persist.PersistView) error {
if err := b.CanSupportNetworkLock(); err != nil {
return err
}
var genesis tka.AUM
if err := genesis.Unserialize(g); err != nil {
return fmt.Errorf("reading genesis: %v", err)
@@ -486,19 +488,21 @@ func (b *LocalBackend) tkaBootstrapFromGenesisLocked(g tkatype.MarshaledAUM, per
}
}
chonkDir := b.chonkPathLocked()
if err := os.Mkdir(filepath.Dir(chonkDir), 0755); err != nil && !os.IsExist(err) {
return fmt.Errorf("creating chonk root dir: %v", err)
root := b.TailscaleVarRoot()
var storage tka.CompactableChonk
if root == "" {
b.health.SetUnhealthy(noNetworkLockStateDirWarnable, nil)
b.logf("network-lock using in-memory storage; no state directory")
storage = tka.ChonkMem()
} else {
chonkDir := b.chonkPathLocked()
chonk, err := tka.ChonkDir(chonkDir)
if err != nil {
return fmt.Errorf("chonk: %v", err)
}
storage = chonk
}
if err := os.Mkdir(chonkDir, 0755); err != nil && !os.IsExist(err) {
return fmt.Errorf("mkdir: %v", err)
}
chonk, err := tka.ChonkDir(chonkDir)
if err != nil {
return fmt.Errorf("chonk: %v", err)
}
authority, err := tka.Bootstrap(chonk, genesis)
authority, err := tka.Bootstrap(storage, genesis)
if err != nil {
return fmt.Errorf("tka bootstrap: %v", err)
}
@@ -506,29 +510,11 @@ func (b *LocalBackend) tkaBootstrapFromGenesisLocked(g tkatype.MarshaledAUM, per
b.tka = &tkaState{
profile: b.pm.CurrentProfile().ID(),
authority: authority,
storage: chonk,
storage: storage,
}
return nil
}
// CanSupportNetworkLock returns nil if tailscaled is able to operate
// a local tailnet key authority (and hence enforce network lock).
func (b *LocalBackend) CanSupportNetworkLock() error {
if b.tka != nil {
// If the TKA is being used, it is supported.
return nil
}
if b.TailscaleVarRoot() == "" {
return errors.New("network-lock is not supported in this configuration, try setting --statedir")
}
// There's a var root (aka --statedir), so if network lock gets
// initialized we have somewhere to store our AUMs. That's all
// we need.
return nil
}
// NetworkLockStatus returns a structure describing the state of the
// tailnet key authority, if any.
func (b *LocalBackend) NetworkLockStatus() *ipnstate.NetworkLockStatus {
@@ -643,10 +629,6 @@ func tkaStateFromPeer(p tailcfg.NodeView) ipnstate.TKAPeer {
// The Finish RPC submits signatures for all these nodes, at which point
// Control has everything it needs to atomically enable network lock.
func (b *LocalBackend) NetworkLockInit(keys []tka.Key, disablementValues [][]byte, supportDisablement []byte) error {
if err := b.CanSupportNetworkLock(); err != nil {
return err
}
var ourNodeKey key.NodePublic
var nlPriv key.NLPrivate
@@ -670,7 +652,7 @@ func (b *LocalBackend) NetworkLockInit(keys []tka.Key, disablementValues [][]byt
// We use an in-memory tailchonk because we don't want to commit to
// the filesystem until we've finished the initialization sequence,
// just in case something goes wrong.
_, genesisAUM, err := tka.Create(&tka.Mem{}, tka.State{
_, genesisAUM, err := tka.Create(tka.ChonkMem(), tka.State{
Keys: keys,
// TODO(tom): s/tka.State.DisablementSecrets/tka.State.DisablementValues
// This will center on consistent nomenclature:
@@ -698,7 +680,7 @@ func (b *LocalBackend) NetworkLockInit(keys []tka.Key, disablementValues [][]byt
// Our genesis AUM was accepted but before Control turns on enforcement of
// node-key signatures, we need to sign keys for all the existing nodes.
// If we don't get these signatures ahead of time, everyone will loose
// If we don't get these signatures ahead of time, everyone will lose
// connectivity because control won't have any signatures to send which
// satisfy network-lock checks.
sigs := make(map[tailcfg.NodeID]tkatype.MarshaledSignature, len(initResp.NeedSignatures))
@@ -762,7 +744,7 @@ func (b *LocalBackend) NetworkLockForceLocalDisable() error {
return fmt.Errorf("saving prefs: %w", err)
}
if err := os.RemoveAll(b.chonkPathLocked()); err != nil {
if err := b.tka.storage.RemoveAll(); err != nil {
return fmt.Errorf("deleting TKA state: %w", err)
}
b.tka = nil
@@ -948,7 +930,7 @@ func (b *LocalBackend) NetworkLockLog(maxEntries int) ([]ipnstate.NetworkLockUpd
if err == os.ErrNotExist {
break
}
return out, fmt.Errorf("reading AUM: %w", err)
return out, fmt.Errorf("reading AUM (%v): %w", cursor, err)
}
update := ipnstate.NetworkLockUpdate{
@@ -1298,27 +1280,10 @@ func (b *LocalBackend) tkaFetchBootstrap(ourNodeKey key.NodePublic, head tka.AUM
return a, nil
}
func fromSyncOffer(offer tka.SyncOffer) (head string, ancestors []string, err error) {
headBytes, err := offer.Head.MarshalText()
if err != nil {
return "", nil, fmt.Errorf("head.MarshalText: %v", err)
}
ancestors = make([]string, len(offer.Ancestors))
for i, ancestor := range offer.Ancestors {
hash, err := ancestor.MarshalText()
if err != nil {
return "", nil, fmt.Errorf("ancestor[%d].MarshalText: %v", i, err)
}
ancestors[i] = string(hash)
}
return string(headBytes), ancestors, nil
}
// tkaDoSyncOffer sends a /machine/tka/sync/offer RPC to the control plane
// over noise. This is the first of two RPCs implementing tka synchronization.
func (b *LocalBackend) tkaDoSyncOffer(ourNodeKey key.NodePublic, offer tka.SyncOffer) (*tailcfg.TKASyncOfferResponse, error) {
head, ancestors, err := fromSyncOffer(offer)
head, ancestors, err := tka.FromSyncOffer(offer)
if err != nil {
return nil, fmt.Errorf("encoding offer: %v", err)
}
+12 -6
View File
@@ -16,6 +16,7 @@ import (
"tailscale.com/ipn"
"tailscale.com/net/dns"
"tailscale.com/net/tsaddr"
"tailscale.com/syncs"
"tailscale.com/tailcfg"
"tailscale.com/types/dnstype"
"tailscale.com/types/key"
@@ -75,13 +76,14 @@ type nodeBackend struct {
filterAtomic atomic.Pointer[filter.Filter]
// initialized once and immutable
eventClient *eventbus.Client
filterPub *eventbus.Publisher[magicsock.FilterUpdate]
nodeViewsPub *eventbus.Publisher[magicsock.NodeViewsUpdate]
nodeMutsPub *eventbus.Publisher[magicsock.NodeMutationsUpdate]
eventClient *eventbus.Client
filterPub *eventbus.Publisher[magicsock.FilterUpdate]
nodeViewsPub *eventbus.Publisher[magicsock.NodeViewsUpdate]
nodeMutsPub *eventbus.Publisher[magicsock.NodeMutationsUpdate]
derpMapViewPub *eventbus.Publisher[tailcfg.DERPMapView]
// TODO(nickkhyl): maybe use sync.RWMutex?
mu sync.Mutex // protects the following fields
mu syncs.Mutex // protects the following fields
shutdownOnce sync.Once // guards calling [nodeBackend.shutdown]
readyCh chan struct{} // closed by [nodeBackend.ready]; nil after shutdown
@@ -121,6 +123,7 @@ func newNodeBackend(ctx context.Context, logf logger.Logf, bus *eventbus.Bus) *n
nb.filterPub = eventbus.Publish[magicsock.FilterUpdate](nb.eventClient)
nb.nodeViewsPub = eventbus.Publish[magicsock.NodeViewsUpdate](nb.eventClient)
nb.nodeMutsPub = eventbus.Publish[magicsock.NodeMutationsUpdate](nb.eventClient)
nb.derpMapViewPub = eventbus.Publish[tailcfg.DERPMapView](nb.eventClient)
nb.filterPub.Publish(magicsock.FilterUpdate{Filter: nb.filterAtomic.Load()})
return nb
}
@@ -435,6 +438,9 @@ func (nb *nodeBackend) SetNetMap(nm *netmap.NetworkMap) {
if nm != nil {
nv.SelfNode = nm.SelfNode
nv.Peers = nm.Peers
nb.derpMapViewPub.Publish(nm.DERPMap.View())
} else {
nb.derpMapViewPub.Publish(tailcfg.DERPMapView{})
}
nb.nodeViewsPub.Publish(nv)
}
@@ -742,7 +748,7 @@ func dnsConfigForNetmap(nm *netmap.NetworkMap, peers map[tailcfg.NodeID]tailcfg.
}
dcfg.Hosts[fqdn] = ips
}
set(nm.Name, nm.GetAddresses())
set(nm.SelfName(), nm.GetAddresses())
for _, peer := range peers {
set(peer.Name(), peer.Addresses())
}
+52 -6
View File
@@ -5,10 +5,12 @@ package ipnlocal
import (
"cmp"
"crypto"
"crypto/rand"
"encoding/json"
"errors"
"fmt"
"io"
"runtime"
"slices"
"strings"
@@ -19,9 +21,12 @@ import (
"tailscale.com/ipn"
"tailscale.com/ipn/ipnext"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
"tailscale.com/types/logger"
"tailscale.com/types/persist"
"tailscale.com/util/clientmetric"
"tailscale.com/util/eventbus"
"tailscale.com/util/testenv"
)
var debug = envknob.RegisterBool("TS_DEBUG_PROFILES")
@@ -56,6 +61,9 @@ type profileManager struct {
// extHost is the bridge between [profileManager] and the registered [ipnext.Extension]s.
// It may be nil in tests. A nil pointer is a valid, no-op host.
extHost *ExtensionHost
// Override for key.NewEmptyHardwareAttestationKey used for testing.
newEmptyHardwareAttestationKey func() (key.HardwareAttestationKey, error)
}
// SetExtensionHost sets the [ExtensionHost] for the [profileManager].
@@ -654,8 +662,26 @@ func (pm *profileManager) loadSavedPrefs(k ipn.StateKey) (ipn.PrefsView, error)
return ipn.PrefsView{}, err
}
savedPrefs := ipn.NewPrefs()
// if supported by the platform, create an empty hardware attestation key to use when deserializing
// to avoid type exceptions from json.Unmarshaling into an interface{}.
hw, _ := pm.newEmptyHardwareAttestationKey()
savedPrefs.Persist = &persist.Persist{
AttestationKey: hw,
}
if err := ipn.PrefsFromBytes(bs, savedPrefs); err != nil {
return ipn.PrefsView{}, fmt.Errorf("parsing saved prefs: %v", err)
// Try loading again, this time ignoring the AttestationKey contents.
// If that succeeds, there's something wrong with the underlying
// attestation key mechanism (most likely the TPM changed), but we
// should at least proceed with client startup.
origErr := err
savedPrefs.Persist.AttestationKey = &noopAttestationKey{}
if err := ipn.PrefsFromBytes(bs, savedPrefs); err != nil {
return ipn.PrefsView{}, fmt.Errorf("parsing saved prefs: %w", err)
} else {
pm.logf("failed to parse savedPrefs with attestation key (error: %v) but parsing without the attestation key succeeded; will proceed without using the old attestation key", origErr)
}
}
pm.logf("using backend prefs for %q: %v", k, savedPrefs.Pretty())
@@ -839,6 +865,7 @@ func (pm *profileManager) CurrentPrefs() ipn.PrefsView {
// ReadStartupPrefsForTest reads the startup prefs from disk. It is only used for testing.
func ReadStartupPrefsForTest(logf logger.Logf, store ipn.StateStore) (ipn.PrefsView, error) {
testenv.AssertInTest()
bus := eventbus.New()
defer bus.Close()
ht := health.NewTracker(bus) // in tests, don't care about the health status
@@ -900,11 +927,12 @@ func newProfileManagerWithGOOS(store ipn.StateStore, logf logger.Logf, ht *healt
metricProfileCount.Set(int64(len(knownProfiles)))
pm := &profileManager{
goos: goos,
store: store,
knownProfiles: knownProfiles,
logf: logf,
health: ht,
goos: goos,
store: store,
knownProfiles: knownProfiles,
logf: logf,
health: ht,
newEmptyHardwareAttestationKey: key.NewEmptyHardwareAttestationKey,
}
var initialProfile ipn.LoginProfileView
@@ -973,3 +1001,21 @@ var (
metricMigrationError = clientmetric.NewCounter("profiles_migration_error")
metricMigrationSuccess = clientmetric.NewCounter("profiles_migration_success")
)
// noopAttestationKey is a key.HardwareAttestationKey that always successfully
// unmarshals as a zero key.
type noopAttestationKey struct{}
func (n noopAttestationKey) Public() crypto.PublicKey {
panic("noopAttestationKey.Public should not be called; missing IsZero check somewhere?")
}
func (n noopAttestationKey) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) {
panic("noopAttestationKey.Sign should not be called; missing IsZero check somewhere?")
}
func (n noopAttestationKey) MarshalJSON() ([]byte, error) { return nil, nil }
func (n noopAttestationKey) UnmarshalJSON([]byte) error { return nil }
func (n noopAttestationKey) Close() error { return nil }
func (n noopAttestationKey) Clone() key.HardwareAttestationKey { return n }
func (n noopAttestationKey) IsZero() bool { return true }
Loaded 100 of 218 files, more files were not shown because too many files have changed in this diff. Show more