Compare commits
1 Commits
docker_sta
...
marwan/scm
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df49cb24d5 |
@@ -1091,6 +1091,17 @@ func (lc *LocalClient) GetServeConfig(ctx context.Context) (*ipn.ServeConfig, er
|
||||
return getServeConfigFromJSON(body)
|
||||
}
|
||||
|
||||
// GetMemoryServeConfig return the current serve config.
|
||||
//
|
||||
// If the serve config is empty, it returns (nil, nil).
|
||||
func (lc *LocalClient) GetMemoryServeConfig(ctx context.Context) (*ipn.ServeConfig, error) {
|
||||
body, err := lc.send(ctx, "GET", "/localapi/v0/serve-config?memory=true", 200, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting serve config: %w", err)
|
||||
}
|
||||
return getServeConfigFromJSON(body)
|
||||
}
|
||||
|
||||
func getServeConfigFromJSON(body []byte) (sc *ipn.ServeConfig, err error) {
|
||||
if err := json.Unmarshal(body, &sc); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -44,7 +44,7 @@ func newFunnelCommand(e *serveEnv) *ffcli.Command {
|
||||
ShortHelp: "Turn on/off Funnel service",
|
||||
ShortUsage: strings.Join([]string{
|
||||
"funnel <serve-port> {on|off}",
|
||||
"funnel status [--json]",
|
||||
"funnel status [--json] [--memory]",
|
||||
}, "\n "),
|
||||
LongHelp: strings.Join([]string{
|
||||
"Funnel allows you to publish a 'tailscale serve'",
|
||||
@@ -62,6 +62,7 @@ func newFunnelCommand(e *serveEnv) *ffcli.Command {
|
||||
ShortHelp: "show current serve/funnel status",
|
||||
FlagSet: e.newFlags("funnel-status", func(fs *flag.FlagSet) {
|
||||
fs.BoolVar(&e.json, "json", false, "output JSON")
|
||||
fs.BoolVar(&e.memory, "memory", false, "in memory config")
|
||||
}),
|
||||
UsageFunc: usageFunc,
|
||||
},
|
||||
|
||||
@@ -131,6 +131,7 @@ func (e *serveEnv) newFlags(name string, setup func(fs *flag.FlagSet)) *flag.Fla
|
||||
type localServeClient interface {
|
||||
StatusWithoutPeers(context.Context) (*ipnstate.Status, error)
|
||||
GetServeConfig(context.Context) (*ipn.ServeConfig, error)
|
||||
GetMemoryServeConfig(context.Context) (*ipn.ServeConfig, error)
|
||||
SetServeConfig(context.Context, *ipn.ServeConfig) error
|
||||
QueryFeature(ctx context.Context, feature string) (*tailcfg.QueryFeatureResponse, error)
|
||||
WatchIPNBus(ctx context.Context, mask ipn.NotifyWatchOpt) (*tailscale.IPNBusWatcher, error)
|
||||
@@ -146,7 +147,8 @@ type localServeClient interface {
|
||||
// It also contains the flags, as registered with newServeCommand.
|
||||
type serveEnv struct {
|
||||
// flags
|
||||
json bool // output JSON (status only for now)
|
||||
json bool // output JSON (status only for now)
|
||||
memory bool // output memory (status only for now)
|
||||
|
||||
lc localServeClient // localClient interface, specific to serve
|
||||
|
||||
@@ -626,7 +628,13 @@ func (e *serveEnv) handleTCPServeRemove(ctx context.Context, src uint16) error {
|
||||
// - tailscale status
|
||||
// - tailscale status --json
|
||||
func (e *serveEnv) runServeStatus(ctx context.Context, args []string) error {
|
||||
sc, err := e.lc.GetServeConfig(ctx)
|
||||
var sc *ipn.ServeConfig
|
||||
var err error
|
||||
if e.memory {
|
||||
sc, err = e.lc.GetMemoryServeConfig(ctx)
|
||||
} else {
|
||||
sc, err = e.lc.GetServeConfig(ctx)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -80,6 +80,7 @@ func (src *ServeConfig) Clone() *ServeConfig {
|
||||
|
||||
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||
var _ServeConfigCloneNeedsRegeneration = ServeConfig(struct {
|
||||
InMemory bool
|
||||
TCP map[uint16]*TCPPortHandler
|
||||
Web map[HostPort]*WebServerConfig
|
||||
AllowFunnel map[HostPort]bool
|
||||
|
||||
@@ -159,6 +159,8 @@ func (v *ServeConfigView) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v ServeConfigView) InMemory() bool { return v.ж.InMemory }
|
||||
|
||||
func (v ServeConfigView) TCP() views.MapFn[uint16, *TCPPortHandler, TCPPortHandlerView] {
|
||||
return views.MapFnOf(v.ж.TCP, func(t *TCPPortHandler) TCPPortHandlerView {
|
||||
return t.View()
|
||||
@@ -177,6 +179,7 @@ func (v ServeConfigView) AllowFunnel() views.Map[HostPort, bool] {
|
||||
|
||||
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||
var _ServeConfigViewNeedsRegeneration = ServeConfig(struct {
|
||||
InMemory bool
|
||||
TCP map[uint16]*TCPPortHandler
|
||||
Web map[HostPort]*WebServerConfig
|
||||
AllowFunnel map[HostPort]bool
|
||||
|
||||
@@ -242,6 +242,7 @@ type LocalBackend struct {
|
||||
// ServeConfig fields. (also guarded by mu)
|
||||
lastServeConfJSON mem.RO // last JSON that was parsed into serveConfig
|
||||
serveConfig ipn.ServeConfigView // or !Valid if none
|
||||
memServeConfig ipn.ServeConfigView // or !Valid if none
|
||||
|
||||
serveListeners map[netip.AddrPort]*serveListener // addrPort => serveListener
|
||||
serveProxyHandlers sync.Map // string (HTTPHandler.Proxy) => *httputil.ReverseProxy
|
||||
@@ -2329,6 +2330,7 @@ func (b *LocalBackend) setAtomicValuesFromPrefsLocked(p ipn.PrefsView) {
|
||||
b.setTCPPortsIntercepted(nil)
|
||||
b.lastServeConfJSON = mem.B(nil)
|
||||
b.serveConfig = ipn.ServeConfigView{}
|
||||
b.memServeConfig = ipn.ServeConfigView{}
|
||||
} else {
|
||||
filtered := tsaddr.FilterPrefixesCopy(p.AdvertiseRoutes(), tsaddr.IsViaPrefix)
|
||||
b.containsViaIPFuncAtomic.Store(tsaddr.NewContainsIPFunc(filtered))
|
||||
@@ -2686,7 +2688,7 @@ func (b *LocalBackend) checkExitNodePrefsLocked(p *ipn.Prefs) error {
|
||||
}
|
||||
|
||||
func (b *LocalBackend) checkFunnelEnabledLocked(p *ipn.Prefs) error {
|
||||
if p.ShieldsUp && b.serveConfig.IsFunnelOn() {
|
||||
if p.ShieldsUp && (b.serveConfig.IsFunnelOn() || b.memServeConfig.IsFunnelOn()) {
|
||||
return errors.New("Cannot enable shields-up when Funnel is enabled.")
|
||||
}
|
||||
return nil
|
||||
@@ -2765,7 +2767,8 @@ func (b *LocalBackend) SetPrefs(newp *ipn.Prefs) {
|
||||
// doesn't affect security or correctness. And we also don't expect people to
|
||||
// modify their ServeConfig in raw mode.
|
||||
func (b *LocalBackend) wantIngressLocked() bool {
|
||||
return b.serveConfig.Valid() && b.serveConfig.AllowFunnel().Len() > 0
|
||||
return b.serveConfig.Valid() && (b.serveConfig.AllowFunnel().Len() > 0) ||
|
||||
b.memServeConfig.Valid() && (b.memServeConfig.AllowFunnel().Len() > 0)
|
||||
}
|
||||
|
||||
// setPrefsLockedOnEntry requires b.mu be held to call it, but it
|
||||
@@ -4073,6 +4076,7 @@ func (b *LocalBackend) reloadServeConfigLocked(prefs ipn.PrefsView) {
|
||||
// Don't try to load the serve config.
|
||||
b.lastServeConfJSON = mem.B(nil)
|
||||
b.serveConfig = ipn.ServeConfigView{}
|
||||
// b.memServeConfig = ipn.ServeConfigView{} should we do this?
|
||||
return
|
||||
}
|
||||
confKey := ipn.ServeConfigKey(b.pm.CurrentProfile().ID)
|
||||
@@ -4082,6 +4086,7 @@ func (b *LocalBackend) reloadServeConfigLocked(prefs ipn.PrefsView) {
|
||||
if err != nil {
|
||||
b.lastServeConfJSON = mem.B(nil)
|
||||
b.serveConfig = ipn.ServeConfigView{}
|
||||
// b.memServeConfig = ipn.ServeConfigView{} should we do this?
|
||||
return
|
||||
}
|
||||
if b.lastServeConfJSON.Equal(mem.B(confj)) {
|
||||
@@ -4092,6 +4097,7 @@ func (b *LocalBackend) reloadServeConfigLocked(prefs ipn.PrefsView) {
|
||||
if err := json.Unmarshal(confj, &conf); err != nil {
|
||||
b.logf("invalid ServeConfig %q in StateStore: %v", confKey, err)
|
||||
b.serveConfig = ipn.ServeConfigView{}
|
||||
// b.memServeConfig = ipn.ServeConfigView{} should we do this?
|
||||
return
|
||||
}
|
||||
b.serveConfig = conf.View()
|
||||
@@ -4109,9 +4115,13 @@ func (b *LocalBackend) setTCPPortsInterceptedFromNetmapAndPrefsLocked(prefs ipn.
|
||||
}
|
||||
|
||||
b.reloadServeConfigLocked(prefs)
|
||||
if b.serveConfig.Valid() {
|
||||
|
||||
setServeProxy := func(sc ipn.ServeConfigView) {
|
||||
if !sc.Valid() {
|
||||
return
|
||||
}
|
||||
servePorts := make([]uint16, 0, 3)
|
||||
b.serveConfig.TCP().Range(func(port uint16, _ ipn.TCPPortHandlerView) bool {
|
||||
sc.TCP().Range(func(port uint16, _ ipn.TCPPortHandlerView) bool {
|
||||
if port > 0 {
|
||||
servePorts = append(servePorts, uint16(port))
|
||||
}
|
||||
@@ -4126,6 +4136,9 @@ func (b *LocalBackend) setTCPPortsInterceptedFromNetmapAndPrefsLocked(prefs ipn.
|
||||
b.updateServeTCPPortNetMapAddrListenersLocked(servePorts)
|
||||
}
|
||||
}
|
||||
setServeProxy(b.serveConfig)
|
||||
setServeProxy(b.memServeConfig)
|
||||
|
||||
// Kick off a Hostinfo update to control if WireIngress changed.
|
||||
if wire := b.wantIngressLocked(); b.hostinfo != nil && b.hostinfo.WireIngress != wire {
|
||||
b.logf("Hostinfo.WireIngress changed to %v", wire)
|
||||
@@ -4140,35 +4153,39 @@ func (b *LocalBackend) setTCPPortsInterceptedFromNetmapAndPrefsLocked(prefs ipn.
|
||||
// backend specified in serveConfig. It expects serveConfig to be valid and
|
||||
// up-to-date, so should be called after reloadServeConfigLocked.
|
||||
func (b *LocalBackend) setServeProxyHandlersLocked() {
|
||||
if !b.serveConfig.Valid() {
|
||||
return
|
||||
}
|
||||
var backends map[string]bool
|
||||
b.serveConfig.Web().Range(func(_ ipn.HostPort, conf ipn.WebServerConfigView) (cont bool) {
|
||||
conf.Handlers().Range(func(_ string, h ipn.HTTPHandlerView) (cont bool) {
|
||||
backend := h.Proxy()
|
||||
if backend == "" {
|
||||
// Only create proxy handlers for servers with a proxy backend.
|
||||
return true
|
||||
}
|
||||
mak.Set(&backends, backend, true)
|
||||
if _, ok := b.serveProxyHandlers.Load(backend); ok {
|
||||
return true
|
||||
}
|
||||
f := func(sc ipn.ServeConfigView) {
|
||||
if !sc.Valid() {
|
||||
return
|
||||
}
|
||||
sc.Web().Range(func(_ ipn.HostPort, conf ipn.WebServerConfigView) (cont bool) {
|
||||
conf.Handlers().Range(func(_ string, h ipn.HTTPHandlerView) (cont bool) {
|
||||
backend := h.Proxy()
|
||||
if backend == "" {
|
||||
// Only create proxy handlers for servers with a proxy backend.
|
||||
return true
|
||||
}
|
||||
mak.Set(&backends, backend, true)
|
||||
if _, ok := b.serveProxyHandlers.Load(backend); ok {
|
||||
return true
|
||||
}
|
||||
|
||||
b.logf("serve: creating a new proxy handler for %s", backend)
|
||||
p, err := b.proxyHandlerForBackend(backend)
|
||||
if err != nil {
|
||||
// The backend endpoint (h.Proxy) should have been validated by expandProxyTarget
|
||||
// in the CLI, so just log the error here.
|
||||
b.logf("[unexpected] could not create proxy for %v: %s", backend, err)
|
||||
b.logf("serve: creating a new proxy handler for %s", backend)
|
||||
p, err := b.proxyHandlerForBackend(backend)
|
||||
if err != nil {
|
||||
// The backend endpoint (h.Proxy) should have been validated by expandProxyTarget
|
||||
// in the CLI, so just log the error here.
|
||||
b.logf("[unexpected] could not create proxy for %v: %s", backend, err)
|
||||
return true
|
||||
}
|
||||
b.serveProxyHandlers.Store(backend, p)
|
||||
return true
|
||||
}
|
||||
b.serveProxyHandlers.Store(backend, p)
|
||||
})
|
||||
return true
|
||||
})
|
||||
return true
|
||||
})
|
||||
}
|
||||
f(b.serveConfig)
|
||||
f(b.memServeConfig)
|
||||
|
||||
// Clean up handlers for proxy backends that are no longer present
|
||||
// in configuration.
|
||||
@@ -4937,7 +4954,8 @@ func (b *LocalBackend) resetForProfileChangeLockedOnEntry() error {
|
||||
}
|
||||
b.lastServeConfJSON = mem.B(nil)
|
||||
b.serveConfig = ipn.ServeConfigView{}
|
||||
b.enterStateLockedOnEntry(ipn.NoState) // Reset state.
|
||||
b.memServeConfig = ipn.ServeConfigView{} // is this needed?
|
||||
b.enterStateLockedOnEntry(ipn.NoState) // Reset state.
|
||||
health.SetLocalLogConfigHealth(nil)
|
||||
return b.Start(ipn.Options{})
|
||||
}
|
||||
|
||||
@@ -231,19 +231,24 @@ func (b *LocalBackend) SetServeConfig(config *ipn.ServeConfig) error {
|
||||
if !nm.SelfNode.Valid() {
|
||||
return errors.New("netMap SelfNode is nil")
|
||||
}
|
||||
profileID := b.pm.CurrentProfile().ID
|
||||
confKey := ipn.ServeConfigKey(profileID)
|
||||
|
||||
var bs []byte
|
||||
if config != nil {
|
||||
j, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encoding serve config: %w", err)
|
||||
if !config.InMemory {
|
||||
profileID := b.pm.CurrentProfile().ID
|
||||
confKey := ipn.ServeConfigKey(profileID)
|
||||
|
||||
var bs []byte
|
||||
if config != nil {
|
||||
j, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encoding serve config: %w", err)
|
||||
}
|
||||
bs = j
|
||||
}
|
||||
bs = j
|
||||
}
|
||||
if err := b.store.WriteState(confKey, bs); err != nil {
|
||||
return fmt.Errorf("writing ServeConfig to StateStore: %w", err)
|
||||
if err := b.store.WriteState(confKey, bs); err != nil {
|
||||
return fmt.Errorf("writing ServeConfig to StateStore: %w", err)
|
||||
}
|
||||
} else {
|
||||
b.memServeConfig = config.View()
|
||||
}
|
||||
|
||||
b.setTCPPortsInterceptedFromNetmapAndPrefsLocked(b.pm.CurrentPrefs())
|
||||
@@ -252,9 +257,12 @@ func (b *LocalBackend) SetServeConfig(config *ipn.ServeConfig) error {
|
||||
|
||||
// ServeConfig provides a view of the current serve mappings.
|
||||
// If serving is not configured, the returned view is not Valid.
|
||||
func (b *LocalBackend) ServeConfig() ipn.ServeConfigView {
|
||||
func (b *LocalBackend) ServeConfig(inMemory bool) ipn.ServeConfigView {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
if inMemory {
|
||||
return b.memServeConfig
|
||||
}
|
||||
return b.serveConfig
|
||||
}
|
||||
|
||||
@@ -278,9 +286,9 @@ func (b *LocalBackend) StreamServe(ctx context.Context, w io.Writer, req ipn.Ser
|
||||
}
|
||||
|
||||
// Turn on Funnel for the given HostPort.
|
||||
sc := b.ServeConfig().AsStruct()
|
||||
sc := b.ServeConfig(true).AsStruct()
|
||||
if sc == nil {
|
||||
sc = &ipn.ServeConfig{}
|
||||
sc = &ipn.ServeConfig{InMemory: true}
|
||||
}
|
||||
setHandler(sc, req)
|
||||
if err := b.SetServeConfig(sc); err != nil {
|
||||
@@ -288,7 +296,7 @@ func (b *LocalBackend) StreamServe(ctx context.Context, w io.Writer, req ipn.Ser
|
||||
}
|
||||
// Defer turning off Funnel once stream ends.
|
||||
defer func() {
|
||||
sc := b.ServeConfig().AsStruct()
|
||||
sc := b.ServeConfig(true).AsStruct()
|
||||
deleteHandler(sc, req, port)
|
||||
err = errors.Join(err, b.SetServeConfig(sc))
|
||||
}()
|
||||
@@ -419,58 +427,63 @@ func (b *LocalBackend) maybeLogServeConnection(destPort uint16, srcAddr netip.Ad
|
||||
func (b *LocalBackend) HandleIngressTCPConn(ingressPeer tailcfg.NodeView, target ipn.HostPort, srcAddr netip.AddrPort, getConnOrReset func() (net.Conn, bool), sendRST func()) {
|
||||
b.mu.Lock()
|
||||
sc := b.serveConfig
|
||||
msc := b.memServeConfig
|
||||
b.mu.Unlock()
|
||||
|
||||
if !sc.Valid() {
|
||||
b.logf("localbackend: got ingress conn w/o serveConfig; rejecting")
|
||||
sendRST()
|
||||
return
|
||||
}
|
||||
|
||||
if !sc.AllowFunnel().Get(target) {
|
||||
b.logf("localbackend: got ingress conn for unconfigured %q; rejecting", target)
|
||||
sendRST()
|
||||
return
|
||||
}
|
||||
|
||||
_, port, err := net.SplitHostPort(string(target))
|
||||
if err != nil {
|
||||
b.logf("localbackend: got ingress conn for bad target %q; rejecting", target)
|
||||
sendRST()
|
||||
return
|
||||
}
|
||||
port16, err := strconv.ParseUint(port, 10, 16)
|
||||
if err != nil {
|
||||
b.logf("localbackend: got ingress conn for bad target %q; rejecting", target)
|
||||
sendRST()
|
||||
return
|
||||
}
|
||||
dport := uint16(port16)
|
||||
if b.getTCPHandlerForFunnelFlow != nil {
|
||||
handler := b.getTCPHandlerForFunnelFlow(srcAddr, dport)
|
||||
if handler != nil {
|
||||
c, ok := getConnOrReset()
|
||||
if !ok {
|
||||
b.logf("localbackend: getConn didn't complete from %v to port %v", srcAddr, dport)
|
||||
return
|
||||
}
|
||||
handler(c)
|
||||
f := func(sc ipn.ServeConfigView) {
|
||||
if !sc.Valid() {
|
||||
b.logf("localbackend: got ingress conn w/o serveConfig; rejecting")
|
||||
sendRST()
|
||||
return
|
||||
}
|
||||
|
||||
if !sc.AllowFunnel().Get(target) {
|
||||
b.logf("localbackend: got ingress conn for unconfigured %q; rejecting", target)
|
||||
sendRST()
|
||||
return
|
||||
}
|
||||
|
||||
_, port, err := net.SplitHostPort(string(target))
|
||||
if err != nil {
|
||||
b.logf("localbackend: got ingress conn for bad target %q; rejecting", target)
|
||||
sendRST()
|
||||
return
|
||||
}
|
||||
port16, err := strconv.ParseUint(port, 10, 16)
|
||||
if err != nil {
|
||||
b.logf("localbackend: got ingress conn for bad target %q; rejecting", target)
|
||||
sendRST()
|
||||
return
|
||||
}
|
||||
dport := uint16(port16)
|
||||
if b.getTCPHandlerForFunnelFlow != nil {
|
||||
handler := b.getTCPHandlerForFunnelFlow(srcAddr, dport)
|
||||
if handler != nil {
|
||||
c, ok := getConnOrReset()
|
||||
if !ok {
|
||||
b.logf("localbackend: getConn didn't complete from %v to port %v", srcAddr, dport)
|
||||
return
|
||||
}
|
||||
handler(c)
|
||||
return
|
||||
}
|
||||
}
|
||||
// TODO(bradfitz): pass ingressPeer etc in context to tcpHandlerForServe,
|
||||
// extend serveHTTPContext or similar.
|
||||
handler := b.tcpHandlerForServe(dport, srcAddr)
|
||||
if handler == nil {
|
||||
sendRST()
|
||||
return
|
||||
}
|
||||
c, ok := getConnOrReset()
|
||||
if !ok {
|
||||
b.logf("localbackend: getConn didn't complete from %v to port %v", srcAddr, dport)
|
||||
return
|
||||
}
|
||||
handler(c)
|
||||
}
|
||||
// TODO(bradfitz): pass ingressPeer etc in context to tcpHandlerForServe,
|
||||
// extend serveHTTPContext or similar.
|
||||
handler := b.tcpHandlerForServe(dport, srcAddr)
|
||||
if handler == nil {
|
||||
sendRST()
|
||||
return
|
||||
}
|
||||
c, ok := getConnOrReset()
|
||||
if !ok {
|
||||
b.logf("localbackend: getConn didn't complete from %v to port %v", srcAddr, dport)
|
||||
return
|
||||
}
|
||||
handler(c)
|
||||
f(sc)
|
||||
f(msc)
|
||||
}
|
||||
|
||||
// tcpHandlerForServe returns a handler for a TCP connection to be served via
|
||||
@@ -478,90 +491,100 @@ func (b *LocalBackend) HandleIngressTCPConn(ingressPeer tailcfg.NodeView, target
|
||||
func (b *LocalBackend) tcpHandlerForServe(dport uint16, srcAddr netip.AddrPort) (handler func(net.Conn) error) {
|
||||
b.mu.Lock()
|
||||
sc := b.serveConfig
|
||||
msc := b.memServeConfig
|
||||
b.mu.Unlock()
|
||||
|
||||
if !sc.Valid() {
|
||||
b.logf("[unexpected] localbackend: got TCP conn w/o serveConfig; from %v to port %v", srcAddr, dport)
|
||||
return nil
|
||||
}
|
||||
|
||||
tcph, ok := sc.TCP().GetOk(dport)
|
||||
if !ok {
|
||||
b.logf("[unexpected] localbackend: got TCP conn without TCP config for port %v; from %v", dport, srcAddr)
|
||||
return nil
|
||||
}
|
||||
|
||||
if tcph.HTTPS() || tcph.HTTP() {
|
||||
hs := &http.Server{
|
||||
Handler: http.HandlerFunc(b.serveWebHandler),
|
||||
BaseContext: func(_ net.Listener) context.Context {
|
||||
return context.WithValue(context.Background(), serveHTTPContextKey{}, &serveHTTPContext{
|
||||
SrcAddr: srcAddr,
|
||||
DestPort: dport,
|
||||
})
|
||||
},
|
||||
f := func(sc ipn.ServeConfigView) (handler func(net.Conn) error) {
|
||||
if !sc.Valid() {
|
||||
// TODO: should log only if both configs are invalid
|
||||
b.logf("[unexpected] localbackend: got TCP conn w/o serveConfig; from %v to port %v", srcAddr, dport)
|
||||
return nil
|
||||
}
|
||||
if tcph.HTTPS() {
|
||||
hs.TLSConfig = &tls.Config{
|
||||
GetCertificate: b.getTLSServeCertForPort(dport),
|
||||
|
||||
tcph, ok := sc.TCP().GetOk(dport)
|
||||
if !ok {
|
||||
// TODO: should log only if both configs are not ok
|
||||
b.logf("[unexpected] localbackend: got TCP conn without TCP config for port %v; from %v", dport, srcAddr)
|
||||
return nil
|
||||
}
|
||||
|
||||
if tcph.HTTPS() || tcph.HTTP() {
|
||||
hs := &http.Server{
|
||||
Handler: http.HandlerFunc(b.serveWebHandler),
|
||||
BaseContext: func(_ net.Listener) context.Context {
|
||||
return context.WithValue(context.Background(), serveHTTPContextKey{}, &serveHTTPContext{
|
||||
SrcAddr: srcAddr,
|
||||
DestPort: dport,
|
||||
})
|
||||
},
|
||||
}
|
||||
if tcph.HTTPS() {
|
||||
hs.TLSConfig = &tls.Config{
|
||||
GetCertificate: b.getTLSServeCertForPort(dport),
|
||||
}
|
||||
return func(c net.Conn) error {
|
||||
return hs.ServeTLS(netutil.NewOneConnListener(c, nil), "", "")
|
||||
}
|
||||
}
|
||||
|
||||
return func(c net.Conn) error {
|
||||
return hs.ServeTLS(netutil.NewOneConnListener(c, nil), "", "")
|
||||
return hs.Serve(netutil.NewOneConnListener(c, nil))
|
||||
}
|
||||
}
|
||||
|
||||
return func(c net.Conn) error {
|
||||
return hs.Serve(netutil.NewOneConnListener(c, nil))
|
||||
if backDst := tcph.TCPForward(); backDst != "" {
|
||||
return func(conn net.Conn) error {
|
||||
defer conn.Close()
|
||||
b.maybeLogServeConnection(dport, srcAddr)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
backConn, err := b.dialer.SystemDial(ctx, "tcp", backDst)
|
||||
cancel()
|
||||
if err != nil {
|
||||
b.logf("localbackend: failed to TCP proxy port %v (from %v) to %s: %v", dport, srcAddr, backDst, err)
|
||||
return nil
|
||||
}
|
||||
defer backConn.Close()
|
||||
if sni := tcph.TerminateTLS(); sni != "" {
|
||||
conn = tls.Server(conn, &tls.Config{
|
||||
GetCertificate: func(hi *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||
defer cancel()
|
||||
pair, err := b.GetCertPEM(ctx, sni, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cert, err := tls.X509KeyPair(pair.CertPEM, pair.KeyPEM)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &cert, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// TODO(bradfitz): do the RegisterIPPortIdentity and
|
||||
// UnregisterIPPortIdentity stuff that netstack does
|
||||
errc := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := io.Copy(backConn, conn)
|
||||
errc <- err
|
||||
}()
|
||||
go func() {
|
||||
_, err := io.Copy(conn, backConn)
|
||||
errc <- err
|
||||
}()
|
||||
return <-errc
|
||||
}
|
||||
}
|
||||
|
||||
b.logf("closing TCP conn to port %v (from %v) with actionless TCPPortHandler", dport, srcAddr)
|
||||
return nil
|
||||
}
|
||||
|
||||
if backDst := tcph.TCPForward(); backDst != "" {
|
||||
return func(conn net.Conn) error {
|
||||
defer conn.Close()
|
||||
b.maybeLogServeConnection(dport, srcAddr)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
backConn, err := b.dialer.SystemDial(ctx, "tcp", backDst)
|
||||
cancel()
|
||||
if err != nil {
|
||||
b.logf("localbackend: failed to TCP proxy port %v (from %v) to %s: %v", dport, srcAddr, backDst, err)
|
||||
return nil
|
||||
}
|
||||
defer backConn.Close()
|
||||
if sni := tcph.TerminateTLS(); sni != "" {
|
||||
conn = tls.Server(conn, &tls.Config{
|
||||
GetCertificate: func(hi *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||
defer cancel()
|
||||
pair, err := b.GetCertPEM(ctx, sni, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cert, err := tls.X509KeyPair(pair.CertPEM, pair.KeyPEM)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &cert, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// TODO(bradfitz): do the RegisterIPPortIdentity and
|
||||
// UnregisterIPPortIdentity stuff that netstack does
|
||||
errc := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := io.Copy(backConn, conn)
|
||||
errc <- err
|
||||
}()
|
||||
go func() {
|
||||
_, err := io.Copy(conn, backConn)
|
||||
errc <- err
|
||||
}()
|
||||
return <-errc
|
||||
}
|
||||
if h := f(sc); h != nil {
|
||||
return h
|
||||
}
|
||||
|
||||
b.logf("closing TCP conn to port %v (from %v) with actionless TCPPortHandler", dport, srcAddr)
|
||||
return nil
|
||||
return f(msc)
|
||||
}
|
||||
|
||||
func getServeHTTPContext(r *http.Request) (c *serveHTTPContext, ok bool) {
|
||||
@@ -825,7 +848,11 @@ func (b *LocalBackend) webServerConfig(hostname string, port uint16) (c ipn.WebS
|
||||
if !b.serveConfig.Valid() {
|
||||
return c, false
|
||||
}
|
||||
return b.serveConfig.Web().GetOk(key)
|
||||
wc, ok := b.serveConfig.Web().GetOk(key)
|
||||
if ok {
|
||||
return wc, ok
|
||||
}
|
||||
return b.memServeConfig.Web().GetOk(key)
|
||||
}
|
||||
|
||||
func (b *LocalBackend) getTLSServeCertForPort(port uint16) func(hi *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
|
||||
@@ -835,7 +835,7 @@ func (h *Handler) serveServeConfig(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
config := h.b.ServeConfig()
|
||||
config := h.b.ServeConfig(r.FormValue("memory") == "true")
|
||||
json.NewEncoder(w).Encode(config)
|
||||
case "POST":
|
||||
if !h.PermitWrite {
|
||||
|
||||
@@ -26,6 +26,11 @@ func ServeConfigKey(profileID ProfileID) StateKey {
|
||||
// ServeConfig is the JSON type stored in the StateStore for
|
||||
// StateKey "_serve/$PROFILE_ID" as returned by ServeConfigKey.
|
||||
type ServeConfig struct {
|
||||
// InMemory indicates whether this config
|
||||
// is persisted in the local store or is
|
||||
// an in memory config
|
||||
InMemory bool
|
||||
|
||||
// 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"`
|
||||
|
||||
Reference in New Issue
Block a user