This commit is contained in:
2026-02-19 10:07:43 +00:00
parent 007438e372
commit 6e637ecf77
1763 changed files with 60820 additions and 279516 deletions

367
vendor/tailscale.com/ipn/serve.go generated vendored
View File

@@ -10,6 +10,7 @@ import (
"net"
"net/netip"
"net/url"
"runtime"
"slices"
"strconv"
"strings"
@@ -17,6 +18,7 @@ import (
"tailscale.com/ipn/ipnstate"
"tailscale.com/tailcfg"
"tailscale.com/types/ipproto"
"tailscale.com/util/dnsname"
"tailscale.com/util/mak"
"tailscale.com/util/set"
)
@@ -149,6 +151,12 @@ type TCPPortHandler struct {
// SNI name with this value. It is only used if TCPForward is non-empty.
// (the HTTPS mode uses ServeConfig.Web)
TerminateTLS string `json:",omitempty"`
// ProxyProtocol indicates whether to send a PROXY protocol header
// before forwarding the connection to TCPForward.
//
// This is only valid if TCPForward is non-empty.
ProxyProtocol int `json:",omitzero"`
}
// HTTPHandler is either a path or a proxy to serve.
@@ -160,32 +168,61 @@ type HTTPHandler struct {
Text string `json:",omitempty"` // plaintext to serve (primarily for testing)
AcceptAppCaps []tailcfg.PeerCapability `json:",omitempty"` // peer capabilities to forward in grant header, e.g. example.com/cap/mon
// 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)
Redirect string `json:",omitempty"`
// TODO(bradfitz): bool to not enumerate directories? TTL on mapping for
// temporary ones? Error codes? Redirects?
// temporary ones? Error codes?
}
// WebHandlerExists reports whether if the ServeConfig Web handler exists for
// the given host:port and mount point.
func (sc *ServeConfig) WebHandlerExists(hp HostPort, mount string) bool {
h := sc.GetWebHandler(hp, mount)
func (sc *ServeConfig) WebHandlerExists(svcName tailcfg.ServiceName, hp HostPort, mount string) bool {
h := sc.GetWebHandler(svcName, hp, mount)
return h != nil
}
// GetWebHandler returns the HTTPHandler for the given host:port and mount point.
// Returns nil if the handler does not exist.
func (sc *ServeConfig) GetWebHandler(hp HostPort, mount string) *HTTPHandler {
if sc == nil || sc.Web[hp] == nil {
func (sc *ServeConfig) GetWebHandler(svcName tailcfg.ServiceName, hp HostPort, mount string) *HTTPHandler {
if sc == nil {
return nil
}
if svcName != "" {
if svc, ok := sc.Services[svcName]; ok && svc.Web != nil {
if webCfg, ok := svc.Web[hp]; ok {
return webCfg.Handlers[mount]
}
}
return nil
}
if sc.Web[hp] == nil {
return nil
}
return sc.Web[hp].Handlers[mount]
}
// GetTCPPortHandler returns the TCPPortHandler for the given port.
// If the port is not configured, nil is returned.
func (sc *ServeConfig) GetTCPPortHandler(port uint16) *TCPPortHandler {
// GetTCPPortHandler returns the TCPPortHandler for the given port. If the port
// is not configured, nil is returned. Parameter svcName can be tailcfg.NoService
// for local serve or a service name for a service hosted on node.
func (sc *ServeConfig) GetTCPPortHandler(port uint16, svcName tailcfg.ServiceName) *TCPPortHandler {
if sc == nil {
return nil
}
if svcName != "" {
if svc, ok := sc.Services[svcName]; ok && svc != nil {
return svc.TCP[port]
}
return nil
}
return sc.TCP[port]
}
@@ -202,6 +239,20 @@ func (sc *ServeConfig) HasPathHandler() bool {
}
}
if sc.Services != nil {
for _, serviceConfig := range sc.Services {
if serviceConfig.Web != nil {
for _, webServerConfig := range serviceConfig.Web {
for _, httpHandler := range webServerConfig.Handlers {
if httpHandler.Path != "" {
return true
}
}
}
}
}
}
if sc.Foreground != nil {
for _, fgConfig := range sc.Foreground {
if fgConfig.HasPathHandler() {
@@ -227,34 +278,78 @@ func (sc *ServeConfig) IsTCPForwardingAny() bool {
return false
}
// IsTCPForwardingOnPort reports whether if ServeConfig is currently forwarding
// in TCPForward mode on the given port. This is exclusive of Web/HTTPS serving.
func (sc *ServeConfig) IsTCPForwardingOnPort(port uint16) bool {
if sc == nil || sc.TCP[port] == nil {
// IsTCPForwardingOnPort reports whether ServeConfig is currently forwarding
// in TCPForward mode on the given port for local or a service. svcName will
// either be noService (empty string) for local serve or a serviceName for service
// hosted on node. Notice TCPForwarding is exclusive with Web/HTTPS serving.
func (sc *ServeConfig) IsTCPForwardingOnPort(port uint16, svcName tailcfg.ServiceName) bool {
if sc == nil {
return false
}
return !sc.IsServingWeb(port)
}
// IsServingWeb reports whether if ServeConfig is currently serving Web
// (HTTP/HTTPS) on the given port. This is exclusive of TCPForwarding.
func (sc *ServeConfig) IsServingWeb(port uint16) bool {
return sc.IsServingHTTP(port) || sc.IsServingHTTPS(port)
}
// IsServingHTTPS reports whether if ServeConfig is currently serving HTTPS on
// the given port. This is exclusive of HTTP and TCPForwarding.
func (sc *ServeConfig) IsServingHTTPS(port uint16) bool {
if sc == nil || sc.TCP[port] == nil {
if svcName != "" {
svc, ok := sc.Services[svcName]
if !ok || svc == nil {
return false
}
if svc.TCP[port] == nil {
return false
}
} else if sc.TCP[port] == nil {
return false
}
return sc.TCP[port].HTTPS
return !sc.IsServingWeb(port, svcName)
}
// IsServingHTTP reports whether if ServeConfig is currently serving HTTP on the
// given port. This is exclusive of HTTPS and TCPForwarding.
func (sc *ServeConfig) IsServingHTTP(port uint16) bool {
if sc == nil || sc.TCP[port] == nil {
// IsServingWeb reports whether ServeConfig is currently serving Web (HTTP/HTTPS)
// on the given port for local or a service. svcName will be either tailcfg.NoService,
// or a serviceName for service hosted on node. This is exclusive with TCPForwarding.
func (sc *ServeConfig) IsServingWeb(port uint16, svcName tailcfg.ServiceName) bool {
return sc.IsServingHTTP(port, svcName) || sc.IsServingHTTPS(port, svcName)
}
// IsServingHTTPS reports whether ServeConfig is currently serving HTTPS on
// the given port for local or a service. svcName will be either tailcfg.NoService
// for local serve, or a serviceName for service hosted on node. This is exclusive
// with HTTP and TCPForwarding.
func (sc *ServeConfig) IsServingHTTPS(port uint16, svcName tailcfg.ServiceName) bool {
if sc == nil {
return false
}
var tcpHandlers map[uint16]*TCPPortHandler
if svcName != "" {
if svc := sc.Services[svcName]; svc != nil {
tcpHandlers = svc.TCP
}
} else {
tcpHandlers = sc.TCP
}
th := tcpHandlers[port]
if th == nil {
return false
}
return th.HTTPS
}
// IsServingHTTP reports whether ServeConfig is currently serving HTTP on the
// given port for local or a service. svcName will be either tailcfg.NoService for
// local serve, or a serviceName for service hosted on node. This is exclusive
// with HTTPS and TCPForwarding.
func (sc *ServeConfig) IsServingHTTP(port uint16, svcName tailcfg.ServiceName) bool {
if sc == nil {
return false
}
if svcName != "" {
if svc := sc.Services[svcName]; svc != nil {
if svc.TCP[port] != nil {
return svc.TCP[port].HTTP
}
}
return false
}
if sc.TCP[port] == nil {
return false
}
return sc.TCP[port].HTTP
@@ -280,21 +375,38 @@ func (sc *ServeConfig) FindConfig(port uint16) (*ServeConfig, bool) {
// SetWebHandler sets the given HTTPHandler at the specified host, port,
// and mount in the serve config. sc.TCP is also updated to reflect web
// serving usage of the given port.
func (sc *ServeConfig) SetWebHandler(handler *HTTPHandler, host string, port uint16, mount string, useTLS bool) {
// serving usage of the given port. The st argument is needed when setting
// a web handler for a service, otherwise it can be nil. mds is the Magic DNS
// suffix, which is used to recreate serve's host.
func (sc *ServeConfig) SetWebHandler(handler *HTTPHandler, host string, port uint16, mount string, useTLS bool, mds string) {
if sc == nil {
sc = new(ServeConfig)
}
mak.Set(&sc.TCP, port, &TCPPortHandler{HTTPS: useTLS, HTTP: !useTLS})
hp := HostPort(net.JoinHostPort(host, strconv.Itoa(int(port))))
if _, ok := sc.Web[hp]; !ok {
mak.Set(&sc.Web, hp, new(WebServerConfig))
tcpMap := &sc.TCP
webServerMap := &sc.Web
hostName := host
if svcName := tailcfg.AsServiceName(host); svcName != "" {
hostName = strings.Join([]string{svcName.WithoutPrefix(), mds}, ".")
svc, ok := sc.Services[svcName]
if !ok {
svc = new(ServiceConfig)
mak.Set(&sc.Services, svcName, svc)
}
tcpMap = &svc.TCP
webServerMap = &svc.Web
}
mak.Set(&sc.Web[hp].Handlers, mount, handler)
mak.Set(tcpMap, port, &TCPPortHandler{HTTPS: useTLS, HTTP: !useTLS})
hp := HostPort(net.JoinHostPort(hostName, strconv.Itoa(int(port))))
webCfg, ok := (*webServerMap)[hp]
if !ok {
webCfg = new(WebServerConfig)
mak.Set(webServerMap, hp, webCfg)
}
mak.Set(&webCfg.Handlers, mount, handler)
// TODO(tylersmalley): handle multiple web handlers from foreground mode
for k, v := range sc.Web[hp].Handlers {
for k, v := range webCfg.Handlers {
if v == handler {
continue
}
@@ -305,7 +417,7 @@ func (sc *ServeConfig) SetWebHandler(handler *HTTPHandler, host string, port uin
m1 := strings.TrimSuffix(mount, "/")
m2 := strings.TrimSuffix(k, "/")
if m1 == m2 {
delete(sc.Web[hp].Handlers, k)
delete(webCfg.Handlers, k)
}
}
}
@@ -314,16 +426,46 @@ func (sc *ServeConfig) SetWebHandler(handler *HTTPHandler, host string, port uin
// connections from the given port. If terminateTLS is true, TLS connections
// are terminated with only the given host name permitted before passing them
// to the fwdAddr.
func (sc *ServeConfig) SetTCPForwarding(port uint16, fwdAddr string, terminateTLS bool, host string) {
//
// If proxyProtocol is non-zero, the corresponding PROXY protocol version
// header is sent before forwarding the connection.
func (sc *ServeConfig) SetTCPForwarding(port uint16, fwdAddr string, terminateTLS bool, proxyProtocol int, host string) {
if sc == nil {
sc = new(ServeConfig)
}
mak.Set(&sc.TCP, port, &TCPPortHandler{TCPForward: fwdAddr})
mak.Set(&sc.TCP, port, &TCPPortHandler{
TCPForward: fwdAddr,
ProxyProtocol: proxyProtocol, // can be 0
})
if terminateTLS {
sc.TCP[port].TerminateTLS = host
}
}
// SetTCPForwardingForService sets the fwdAddr (IP:port form) to which to
// forward connections from the given port on the service. If terminateTLS
// is true, TLS connections are terminated, with only the FQDN that corresponds
// to the given service being permitted, before passing them to the fwdAddr.
func (sc *ServeConfig) SetTCPForwardingForService(port uint16, fwdAddr string, terminateTLS bool, svcName tailcfg.ServiceName, proxyProtocol int, magicDNSSuffix string) {
if sc == nil {
sc = new(ServeConfig)
}
svcConfig, ok := sc.Services[svcName]
if !ok {
svcConfig = new(ServiceConfig)
mak.Set(&sc.Services, svcName, svcConfig)
}
mak.Set(&svcConfig.TCP, port, &TCPPortHandler{
TCPForward: fwdAddr,
ProxyProtocol: proxyProtocol, // can be 0
})
if terminateTLS {
svcConfig.TCP[port].TerminateTLS = fmt.Sprintf("%s.%s", svcName.WithoutPrefix(), magicDNSSuffix)
}
}
// SetFunnel sets the sc.AllowFunnel value for the given host and port.
func (sc *ServeConfig) SetFunnel(host string, port uint16, setOn bool) {
if sc == nil {
@@ -344,9 +486,9 @@ func (sc *ServeConfig) SetFunnel(host string, port uint16, setOn bool) {
}
}
// RemoveWebHandler deletes the web handlers at all of the given mount points
// for the provided host and port in the serve config. If cleanupFunnel is
// true, this also removes the funnel value for this port if no handlers remain.
// RemoveWebHandler deletes the web handlers at all of the given mount points for the
// provided host and port in the serve config for the node (as opposed to a service).
// If cleanupFunnel is true, this also removes the funnel value for this port if no handlers remain.
func (sc *ServeConfig) RemoveWebHandler(host string, port uint16, mounts []string, cleanupFunnel bool) {
hp := HostPort(net.JoinHostPort(host, strconv.Itoa(int(port))))
@@ -374,9 +516,50 @@ func (sc *ServeConfig) RemoveWebHandler(host string, port uint16, mounts []strin
}
}
// RemoveServiceWebHandler deletes the web handlers at all of the given mount points
// for the provided host and port in the serve config for the given service.
func (sc *ServeConfig) RemoveServiceWebHandler(svcName tailcfg.ServiceName, hostName string, port uint16, mounts []string) {
hp := HostPort(net.JoinHostPort(hostName, strconv.Itoa(int(port))))
svc, ok := sc.Services[svcName]
if !ok || svc == nil {
return
}
// Delete existing handler, then cascade delete if empty.
for _, m := range mounts {
delete(svc.Web[hp].Handlers, m)
}
if len(svc.Web[hp].Handlers) == 0 {
delete(svc.Web, hp)
delete(svc.TCP, port)
}
if len(svc.Web) == 0 && len(svc.TCP) == 0 {
delete(sc.Services, svcName)
}
if len(sc.Services) == 0 {
sc.Services = nil
}
}
// RemoveTCPForwarding deletes the TCP forwarding configuration for the given
// port from the serve config.
func (sc *ServeConfig) RemoveTCPForwarding(port uint16) {
func (sc *ServeConfig) RemoveTCPForwarding(svcName tailcfg.ServiceName, port uint16) {
if svcName != "" {
if svc := sc.Services[svcName]; svc != nil {
delete(svc.TCP, port)
if len(svc.TCP) == 0 {
svc.TCP = nil
}
if len(svc.Web) == 0 && len(svc.TCP) == 0 {
delete(sc.Services, svcName)
}
if len(sc.Services) == 0 {
sc.Services = nil
}
}
return
}
delete(sc.TCP, port)
if len(sc.TCP) == 0 {
sc.TCP = nil
@@ -519,7 +702,8 @@ func CheckFunnelPort(wantedPort uint16, node *ipnstate.PeerStatus) error {
// ExpandProxyTargetValue expands the supported target values to be proxied
// allowing for input values to be a port number, a partial URL, or a full URL
// including a path.
// including a path. If it's for a service, remote addresses are allowed and
// there doesn't have to be a port specified.
//
// examples:
// - 3000
@@ -529,17 +713,40 @@ func CheckFunnelPort(wantedPort uint16, node *ipnstate.PeerStatus) error {
// - https://localhost:3000
// - https-insecure://localhost:3000
// - https-insecure://localhost:3000/foo
// - https://tailscale.com
func ExpandProxyTargetValue(target string, supportedSchemes []string, defaultScheme string) (string, error) {
const host = "127.0.0.1"
// empty target is invalid
if target == "" {
return "", fmt.Errorf("empty target")
}
// support target being a port number
if port, err := strconv.ParseUint(target, 10, 16); err == nil {
return fmt.Sprintf("%s://%s:%d", defaultScheme, host, port), nil
}
// handle unix: scheme specially - it doesn't use standard URL format
if strings.HasPrefix(target, "unix:") {
if !slices.Contains(supportedSchemes, "unix") {
return "", fmt.Errorf("unix sockets are not supported for this target type")
}
if runtime.GOOS == "windows" {
return "", fmt.Errorf("unix socket serve target is not supported on Windows")
}
path := strings.TrimPrefix(target, "unix:")
if path == "" {
return "", fmt.Errorf("unix socket path cannot be empty")
}
return target, nil
}
hasScheme := true
// prepend scheme if not present
if !strings.Contains(target, "://") {
target = defaultScheme + "://" + target
hasScheme = false
}
// make sure we can parse the target
@@ -553,16 +760,28 @@ func ExpandProxyTargetValue(target string, supportedSchemes []string, defaultSch
return "", fmt.Errorf("must be a URL starting with one of the supported schemes: %v", supportedSchemes)
}
// validate the host.
switch u.Hostname() {
case "localhost", "127.0.0.1":
default:
return "", errors.New("only localhost or 127.0.0.1 proxies are currently supported")
// validate port according to host.
if u.Hostname() == "localhost" || u.Hostname() == "127.0.0.1" || u.Hostname() == "::1" {
// require port for localhost targets
if u.Port() == "" {
return "", fmt.Errorf("port required for localhost target %q", target)
}
} else {
validHN := dnsname.ValidHostname(u.Hostname()) == nil
validIP := net.ParseIP(u.Hostname()) != nil
if !validHN && !validIP {
return "", fmt.Errorf("invalid hostname or IP address %q", u.Hostname())
}
// require scheme for non-localhost targets
if !hasScheme {
return "", fmt.Errorf("non-localhost target %q must include a scheme", target)
}
}
// validate the port
port, err := strconv.ParseUint(u.Port(), 10, 16)
if err != nil || port == 0 {
if u.Port() == "" {
return u.String(), nil // allow no port for remote destinations
}
return "", fmt.Errorf("invalid port %q", u.Port())
}
@@ -626,6 +845,7 @@ func (v ServeConfigView) FindServiceTCP(svcName tailcfg.ServiceName, port uint16
return svcCfg.TCP().GetOk(port)
}
// FindServiceWeb returns the web handler for the service's host-port.
func (v ServeConfigView) FindServiceWeb(svcName tailcfg.ServiceName, hp HostPort) (res WebServerConfigView, ok bool) {
if svcCfg, ok := v.Services().GetOk(svcName); ok {
if res, ok := svcCfg.Web().GetOk(hp); ok {
@@ -639,10 +859,9 @@ func (v ServeConfigView) FindServiceWeb(svcName tailcfg.ServiceName, hp HostPort
// prefers a foreground match first followed by a background search if none
// existed.
func (v ServeConfigView) FindTCP(port uint16) (res TCPPortHandlerView, ok bool) {
for _, conf := range v.Foreground().All() {
if res, ok := conf.TCP().GetOk(port); ok {
return res, ok
}
res, ok = v.FindForegroundTCP(port)
if ok {
return res, ok
}
return v.TCP().GetOk(port)
}
@@ -659,6 +878,17 @@ func (v ServeConfigView) FindWeb(hp HostPort) (res WebServerConfigView, ok bool)
return v.Web().GetOk(hp)
}
// FindForegroundTCP returns the first foreground TCP handler matching the input
// port.
func (v ServeConfigView) FindForegroundTCP(port uint16) (res TCPPortHandlerView, ok bool) {
for _, conf := range v.Foreground().All() {
if res, ok := conf.TCP().GetOk(port); ok {
return res, ok
}
}
return res, false
}
// HasAllowFunnel returns whether this config has at least one AllowFunnel
// set in the background or foreground configs.
func (v ServeConfigView) HasAllowFunnel() bool {
@@ -687,17 +917,6 @@ func (v ServeConfigView) HasFunnelForTarget(target HostPort) bool {
return false
}
// CheckValidServicesConfig reports whether the ServeConfig has
// invalid service configurations.
func (sc *ServeConfig) CheckValidServicesConfig() error {
for svcName, service := range sc.Services {
if err := service.checkValidConfig(); err != nil {
return fmt.Errorf("invalid service configuration for %q: %w", svcName, err)
}
}
return nil
}
// ServicePortRange returns the list of tailcfg.ProtoPortRange that represents
// the proto/ports pairs that are being served by the service.
//
@@ -735,17 +954,3 @@ func (v ServiceConfigView) ServicePortRange() []tailcfg.ProtoPortRange {
}
return ranges
}
// ErrServiceConfigHasBothTCPAndTun signals that a service
// in Tun mode cannot also has TCP or Web handlers set.
var ErrServiceConfigHasBothTCPAndTun = errors.New("the VIP Service configuration can not set TUN at the same time as TCP or Web")
// checkValidConfig checks if the service configuration is valid.
// Currently, the only invalid configuration is when the service is in Tun mode
// and has TCP or Web handlers.
func (v *ServiceConfig) checkValidConfig() error {
if v.Tun && (len(v.TCP) > 0 || len(v.Web) > 0) {
return ErrServiceConfigHasBothTCPAndTun
}
return nil
}