Repository: k3s-io/k3s
File: pkg/vpn/vpn.go (Go)

Code before:
}

// getTailscaleInfo returns the IPs of the interface
func getTailscaleInfo() (VPNInfo, error) {
	output, err := util.ExecCommand("tailscale", []string{"status", "--json"})
	if err != nil {
		return VPNInfo{}, fmt.Errorf("failed to run tailscale status --json: %v", err)
	}

	logrus.Debugf("Output from tailscale status --json: %v", output)

	var tailscaleOutput TailscaleOutput
	err = json.Unmarshal([]byte(output), &tailscaleOutput)
	if err != nil {
		return VPNInfo{}, fmt.Errorf("failed to unmarshal tailscale output: %v", err)
	}

	// Errors are ignored because the interface might not have ipv4 or ipv6 addresses (that's the only possible error)
	ipv4Address, _ := util.GetFirst4String(tailscaleOutput.TailscaleIPs)
	ipv6Address, _ := util.GetFirst6String(tailscaleOutput.TailscaleIPs)

	return VPNInfo{BackendState: tailscaleOutput.BackendState, IPv4Address: net.ParseIP(ipv4Address), IPv6Address: net.ParseIP(ipv6Address), NodeID: "", ProviderName: "tailscale", VPNInterface: tailscaleIf}, nil
}

// get Tailscale advertised IP list
func GetAdvertisedIP() (string, error) {
	output, err := util.ExecCommand("tailscale", []string{"debug", "prefs"})
	if err != nil {
		return "", fmt.Errorf("failed to run tailscale debug prefs: %v", err)
	}

	logrus.Debugf("Output from tailscale debug prefs: %v", output)

	var tailscaleOutput TailscalePrefsOutput
	err = json.Unmarshal([]byte(output), &tailscaleOutput)
	if err != nil {
		return "", fmt.Errorf("failed to unmarshal tailscale output: %v", err)
	}

	var builder strings.Builder
	for _, ipAdvertised := range tailscaleOutput.AdvertiseRoutes {
		builder.WriteString(ipAdvertised + ",")
	}
	return builder.String(), nil

}

// processCLIArgs separates the extraArgs part from the command.
// Note that tailscale flags of type list are comma separated and don't accept spaces, thus we can use strings.Fields to separate flags
func processCLIArgs(command string) (string, []string) {
	subCommands := strings.Split(command, ",extraArgs=")

Review comment (suggestion):
The name indicates that this returns a singular IP, but it in fact returns a comma-separated string of all advertised routes. Return a list of string, or the actual type, whatever is easiest to work with on the caller side.
```suggestion
// get Tailscale advertised route list
func GetAdvertisedRoutes() ([]netip.Prefix, error) {
```

Code after:
	return errors.New("Requested VPN: " + authInfo.Name + " is not supported. We currently only support tailscale")
}

// getTailscaleInfo returns the IPs of the interface
func getTailscaleInfo() (VPNInfo, error) {
	output, err := util.ExecCommand("tailscale", []string{"status", "--json"})
	if err != nil {
		return VPNInfo{}, fmt.Errorf("failed to run tailscale status --json: %v", err)
	}

	logrus.Debugf("Output from tailscale status --json: %v", output)

	var tailscaleOutput TailscaleOutput
	err = json.Unmarshal([]byte(output), &tailscaleOutput)
	if err != nil {
		return VPNInfo{}, fmt.Errorf("failed to unmarshal tailscale output: %v", err)
	}

	// Errors are ignored because the interface might not have ipv4 or ipv6 addresses (that's the only possible error)
	ipv4Address, _ := util.GetFirst4String(tailscaleOutput.TailscaleIPs)
	ipv6Address, _ := util.GetFirst6String(tailscaleOutput.TailscaleIPs)

	return VPNInfo{BackendState: tailscaleOutput.BackendState, IPv4Address: net.ParseIP(ipv4Address), IPv6Address: net.ParseIP(ipv6Address), NodeID: "", ProviderName: "tailscale", VPNInterface: tailscaleIf}, nil
}

// get Tailscale advertised route list
func GetAdvertisedRoutes() ([]netip.Prefix, error) {
	output, err := util.ExecCommand("tailscale", []string{"debug", "prefs"})
	if err != nil {
		return nil, fmt.Errorf("failed to run tailscale debug prefs: %v", err)
	}

	logrus.Debugf("Output from tailscale debug prefs: %v", output)

	var tailscaleOutput TailscalePrefsOutput
	err = json.Unmarshal([]byte(output), &tailscaleOutput)
	if err != nil {
		return nil, fmt.Errorf("failed to unmarshal tailscale output: %v", err)
	}

	return tailscaleOutput.AdvertiseRoutes, nil

}

// processCLIArgs separates the extraArgs part from the command.
// Note that tailscale flags of type list are comma separated and don't accept spaces, thus we can use strings.Fields to separate flags
func processCLIArgs(command string) (string, []string) {
	subCommands := strings.Split(command, ",extraArgs=")
	if len(subCommands) > 1 {
		return subCommands[0], strings.Fields(subCommands[1])
	}