@ -18,7 +18,6 @@ import (
"os/signal"
"os/signal"
"path"
"path"
"path/filepath"
"path/filepath"
"slices"
"sort"
"sort"
"strconv"
"strconv"
"strings"
"strings"
@ -334,7 +333,7 @@ func (e *serveEnv) runServeCombined(subcmd serveMode) execFunc {
const backgroundExistsMsg = "background configuration already exists, use `tailscale %s --%s=%d off` to remove the existing configuration"
const backgroundExistsMsg = "background configuration already exists, use `tailscale %s --%s=%d off` to remove the existing configuration"
func ( e * serveEnv ) validateConfig ( sc * ipn . ServeConfig , port uint16 , wantServe serveType ) error {
func ( e * serveEnv ) validateConfig ( sc * ipn . ServeConfig , port uint16 , wantServe serveType ) error {
sc , isFg := findConfig ( sc , port )
sc , isFg := sc . FindConfig ( port )
if sc == nil {
if sc == nil {
return nil
return nil
}
}
@ -366,24 +365,6 @@ func serveFromPortHandler(tcp *ipn.TCPPortHandler) serveType {
}
}
}
}
// findConfig finds a config that contains the given port, which can be
// the top level background config or an inner foreground one. The second
// result is true if it's foreground
func findConfig ( sc * ipn . ServeConfig , port uint16 ) ( * ipn . ServeConfig , bool ) {
if sc == nil {
return nil , false
}
if _ , ok := sc . TCP [ port ] ; ok {
return sc , false
}
for _ , sc := range sc . Foreground {
if _ , ok := sc . TCP [ port ] ; ok {
return sc , true
}
}
return nil , false
}
func ( e * serveEnv ) setServe ( sc * ipn . ServeConfig , st * ipnstate . Status , dnsName string , srvType serveType , srvPort uint16 , mount string , target string , allowFunnel bool ) error {
func ( e * serveEnv ) setServe ( sc * ipn . ServeConfig , st * ipnstate . Status , dnsName string , srvType serveType , srvPort uint16 , mount string , target string , allowFunnel bool ) error {
// update serve config based on the type
// update serve config based on the type
switch srvType {
switch srvType {
@ -535,7 +516,7 @@ func (e *serveEnv) applyWebServe(sc *ipn.ServeConfig, dnsName string, srvPort ui
}
}
h . Path = target
h . Path = target
default :
default :
t , err := expandProxyTargetDev ( target , [ ] string { "http" , "https" , "https+insecure" } , "http" )
t , err := ipn . ExpandProxyTargetValue ( target , [ ] string { "http" , "https" , "https+insecure" } , "http" )
if err != nil {
if err != nil {
return err
return err
}
}
@ -585,7 +566,7 @@ func (e *serveEnv) applyTCPServe(sc *ipn.ServeConfig, dnsName string, srcType se
return fmt . Errorf ( "invalid TCP target %q" , target )
return fmt . Errorf ( "invalid TCP target %q" , target )
}
}
targetURL , err := expandProxyTargetDev ( target , [ ] string { "tcp" } , "tcp" )
targetURL , err := ipn . ExpandProxyTargetValue ( target , [ ] string { "tcp" } , "tcp" )
if err != nil {
if err != nil {
return fmt . Errorf ( "unable to expand target: %v" , err )
return fmt . Errorf ( "unable to expand target: %v" , err )
}
}
@ -865,60 +846,6 @@ func (e *serveEnv) removeTCPServe(sc *ipn.ServeConfig, src uint16) error {
return nil
return nil
}
}
// expandProxyTargetDev 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.
//
// examples:
// - 3000
// - localhost:3000
// - tcp://localhost:3000
// - http://localhost:3000
// - https://localhost:3000
// - https-insecure://localhost:3000
// - https-insecure://localhost:3000/foo
func expandProxyTargetDev ( target string , supportedSchemes [ ] string , defaultScheme string ) ( string , error ) {
const host = "127.0.0.1"
// 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
}
// prepend scheme if not present
if ! strings . Contains ( target , "://" ) {
target = defaultScheme + "://" + target
}
// make sure we can parse the target
u , err := url . ParseRequestURI ( target )
if err != nil {
return "" , fmt . Errorf ( "invalid URL %w" , err )
}
// ensure a supported scheme
if ! slices . Contains ( supportedSchemes , u . Scheme ) {
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 the port
port , err := strconv . ParseUint ( u . Port ( ) , 10 , 16 )
if err != nil || port == 0 {
return "" , fmt . Errorf ( "invalid port %q" , u . Port ( ) )
}
u . Host = fmt . Sprintf ( "%s:%d" , host , port )
return u . String ( ) , nil
}
// cleanURLPath ensures the path is clean and has a leading "/".
// cleanURLPath ensures the path is clean and has a leading "/".
func cleanURLPath ( urlPath string ) ( string , error ) {
func cleanURLPath ( urlPath string ) ( string , error ) {
if urlPath == "" {
if urlPath == "" {