diff --git a/cmd/tailscale/cli/serve_legacy.go b/cmd/tailscale/cli/serve_legacy.go index 443a404ab..c25fd5080 100644 --- a/cmd/tailscale/cli/serve_legacy.go +++ b/cmd/tailscale/cli/serve_legacy.go @@ -161,6 +161,7 @@ type serveEnv struct { tlsTerminatedTCP uint // a TLS terminated TCP port subcmd serveMode // subcommand yes bool // update without prompt + service string // listen on a virtual service IP lc localServeClient // localClient interface, specific to serve diff --git a/cmd/tailscale/cli/serve_v2.go b/cmd/tailscale/cli/serve_v2.go index 009a61198..6446253f5 100644 --- a/cmd/tailscale/cli/serve_v2.go +++ b/cmd/tailscale/cli/serve_v2.go @@ -127,6 +127,7 @@ func newServeV2Command(e *serveEnv, subcmd serveMode) *ffcli.Command { fs.UintVar(&e.tcp, "tcp", 0, "Expose a TCP forwarder to forward raw TCP packets at the specified port") fs.UintVar(&e.tlsTerminatedTCP, "tls-terminated-tcp", 0, "Expose a TCP forwarder to forward TLS-terminated TCP packets at the specified port") fs.BoolVar(&e.yes, "yes", false, "Update without interactive prompts (default false)") + fs.StringVar(&e.service, "service", "", "listen for connections on a virtual service IP (example: service:myservice)") }), UsageFunc: usageFuncNoDefaultValues, Subcommands: []*ffcli.Command{ diff --git a/ipn/serve.go b/ipn/serve.go index 5c0a97ed3..d7c43f193 100644 --- a/ipn/serve.go +++ b/ipn/serve.go @@ -24,9 +24,7 @@ func ServeConfigKey(profileID ProfileID) StateKey { return StateKey("_serve/" + profileID) } -// ServeConfig is the JSON type stored in the StateStore for -// StateKey "_serve/$PROFILE_ID" as returned by ServeConfigKey. -type ServeConfig struct { +type ListenerConfig struct { // TCP are the list of TCP port numbers that tailscaled should handle for // the Tailscale IP addresses. (not subnet routers, etc) TCP map[uint16]*TCPPortHandler `json:",omitempty"` @@ -34,6 +32,14 @@ type ServeConfig struct { // Web maps from "$SNI_NAME:$PORT" to a set of HTTP handlers // keyed by mount point ("/", "/foo", etc) Web map[HostPort]*WebServerConfig `json:",omitempty"` +} + +// ServeConfig is the JSON type stored in the StateStore for +// StateKey "_serve/$PROFILE_ID" as returned by ServeConfigKey. +type ServeConfig struct { + ListenerConfig // local config + + Services map[string]ListenerConfig `json:",omitempty"` // VIP service config // AllowFunnel is the set of SNI:port values for which funnel // traffic is allowed, from trusted ingress peers. diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index 0d4fae3d5..04de358b0 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -2346,6 +2346,9 @@ type Oauth2Token struct { // NodeAttrSSHEnvironmentVariables enables logic for handling environment variables sent // via SendEnv in the SSH server and applying them to the SSH session. NodeAttrSSHEnvironmentVariables NodeCapability = "ssh-env-vars" + + // NodeAttrVIPService instructs the client how to configure VIP services. + NodeAttrVIPService NodeCapability = "vip-service" ) // SetDNSRequest is a request to add a DNS record. @@ -2824,3 +2827,10 @@ type EarlyNoise struct { // For some request types, the header may have multiple values. (e.g. OldNodeKey // vs NodeKey) const LBHeader = "Ts-Lb" + +type VIPServicePortMap map[ /*proto*/ int]map[ /*port*/ int]netip.AddrPort + +type VIPService struct { + Addrs []netip.Addr `json:"addrs,omitempty"` + PortMap VIPServicePortMap `json:"portMap,omitempty"` +} diff --git a/wgengine/netstack/netstack.go b/wgengine/netstack/netstack.go index d029b6c19..d35412d33 100644 --- a/wgengine/netstack/netstack.go +++ b/wgengine/netstack/netstack.go @@ -19,6 +19,7 @@ "sync/atomic" "time" + "github.com/gaissmai/bart" "github.com/tailscale/wireguard-go/conn" "gvisor.dev/gvisor/pkg/refs" "gvisor.dev/gvisor/pkg/tcpip" @@ -173,6 +174,10 @@ type Impl struct { // It can only be set before calling Start. ProcessSubnets bool + // ServiceVIPs is the description of service VIPs that should be handled. + // It can only be set before calling Start and is immutable after. + ServiceVIPs bart.Table[map[int]netip.AddrPort] + ipstack *stack.Stack linkEP *linkEndpoint tundev *tstun.Wrapper