Просмотр исходного кода

ipn: add WebServerConfig, add views

cmd/viewer couldn't deal with that map-of-map. Add a wrapper type
instead, which also gives us a place to add future stuff.

Updates tailscale/corp#7515

Change-Id: I44a4ca1915300ea8678e5b0385056f0642ccb155
Signed-off-by: Brad Fitzpatrick <[email protected]>
Brad Fitzpatrick 3 лет назад
Родитель
Сommit
df5e40f731
6 измененных файлов с 330 добавлено и 6 удалено
  1. 2 0
      ipn/doc.go
  2. 86 0
      ipn/ipn_clone.go
  3. 230 1
      ipn/ipn_view.go
  4. 2 2
      ipn/ipnlocal/local.go
  5. 0 2
      ipn/prefs.go
  6. 10 1
      ipn/store.go

+ 2 - 0
ipn/doc.go

@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:generate go run tailscale.com/cmd/viewer -type=Prefs,ServeConfig,TCPPortHandler,HTTPHandler,WebServerConfig
+
 // Package ipn implements the interactions between the Tailscale cloud
 // control plane and the local network stack.
 //

+ 86 - 0
ipn/ipn_clone.go

@@ -55,3 +55,89 @@ var _PrefsCloneNeedsRegeneration = Prefs(struct {
 	OperatorUser           string
 	Persist                *persist.Persist
 }{})
+
+// Clone makes a deep copy of ServeConfig.
+// The result aliases no memory with the original.
+func (src *ServeConfig) Clone() *ServeConfig {
+	if src == nil {
+		return nil
+	}
+	dst := new(ServeConfig)
+	*dst = *src
+	if dst.TCP != nil {
+		dst.TCP = map[int]*TCPPortHandler{}
+		for k, v := range src.TCP {
+			dst.TCP[k] = v.Clone()
+		}
+	}
+	if dst.Web != nil {
+		dst.Web = map[HostPort]*WebServerConfig{}
+		for k, v := range src.Web {
+			dst.Web[k] = v.Clone()
+		}
+	}
+	return dst
+}
+
+// A compilation failure here means this code must be regenerated, with the command at the top of this file.
+var _ServeConfigCloneNeedsRegeneration = ServeConfig(struct {
+	TCP map[int]*TCPPortHandler
+	Web map[HostPort]*WebServerConfig
+}{})
+
+// Clone makes a deep copy of TCPPortHandler.
+// The result aliases no memory with the original.
+func (src *TCPPortHandler) Clone() *TCPPortHandler {
+	if src == nil {
+		return nil
+	}
+	dst := new(TCPPortHandler)
+	*dst = *src
+	return dst
+}
+
+// A compilation failure here means this code must be regenerated, with the command at the top of this file.
+var _TCPPortHandlerCloneNeedsRegeneration = TCPPortHandler(struct {
+	HTTPS        bool
+	TCPForward   string
+	TerminateTLS bool
+}{})
+
+// Clone makes a deep copy of HTTPHandler.
+// The result aliases no memory with the original.
+func (src *HTTPHandler) Clone() *HTTPHandler {
+	if src == nil {
+		return nil
+	}
+	dst := new(HTTPHandler)
+	*dst = *src
+	return dst
+}
+
+// A compilation failure here means this code must be regenerated, with the command at the top of this file.
+var _HTTPHandlerCloneNeedsRegeneration = HTTPHandler(struct {
+	Path  string
+	Proxy string
+}{})
+
+// Clone makes a deep copy of WebServerConfig.
+// The result aliases no memory with the original.
+func (src *WebServerConfig) Clone() *WebServerConfig {
+	if src == nil {
+		return nil
+	}
+	dst := new(WebServerConfig)
+	*dst = *src
+	if dst.Handlers != nil {
+		dst.Handlers = map[string]*HTTPHandler{}
+		for k, v := range src.Handlers {
+			dst.Handlers[k] = v.Clone()
+		}
+	}
+	return dst
+}
+
+// A compilation failure here means this code must be regenerated, with the command at the top of this file.
+var _WebServerConfigCloneNeedsRegeneration = WebServerConfig(struct {
+	Handlers map[string]*HTTPHandler
+}{})

+ 230 - 1
ipn/ipn_view.go

@@ -17,7 +17,7 @@ import (
 	"tailscale.com/types/views"
 )
 
-//go:generate go run tailscale.com/cmd/cloner  -clonefunc=false -type=Prefs
+//go:generate go run tailscale.com/cmd/cloner  -clonefunc=false -type=Prefs,ServeConfig,TCPPortHandler,HTTPHandler,WebServerConfig
 
 // View returns a readonly view of Prefs.
 func (p *Prefs) View() PrefsView {
@@ -118,3 +118,232 @@ var _PrefsViewNeedsRegeneration = Prefs(struct {
 	OperatorUser           string
 	Persist                *persist.Persist
 }{})
+
+// View returns a readonly view of ServeConfig.
+func (p *ServeConfig) View() ServeConfigView {
+	return ServeConfigView{ж: p}
+}
+
+// ServeConfigView provides a read-only view over ServeConfig.
+//
+// Its methods should only be called if `Valid()` returns true.
+type ServeConfigView struct {
+	// ж is the underlying mutable value, named with a hard-to-type
+	// character that looks pointy like a pointer.
+	// It is named distinctively to make you think of how dangerous it is to escape
+	// to callers. You must not let callers be able to mutate it.
+	ж *ServeConfig
+}
+
+// Valid reports whether underlying value is non-nil.
+func (v ServeConfigView) Valid() bool { return v.ж != nil }
+
+// AsStruct returns a clone of the underlying value which aliases no memory with
+// the original.
+func (v ServeConfigView) AsStruct() *ServeConfig {
+	if v.ж == nil {
+		return nil
+	}
+	return v.ж.Clone()
+}
+
+func (v ServeConfigView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+
+func (v *ServeConfigView) UnmarshalJSON(b []byte) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	if len(b) == 0 {
+		return nil
+	}
+	var x ServeConfig
+	if err := json.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+func (v ServeConfigView) TCP() views.MapFn[int, *TCPPortHandler, TCPPortHandlerView] {
+	return views.MapFnOf(v.ж.TCP, func(t *TCPPortHandler) TCPPortHandlerView {
+		return t.View()
+	})
+}
+
+func (v ServeConfigView) Web() views.MapFn[HostPort, *WebServerConfig, WebServerConfigView] {
+	return views.MapFnOf(v.ж.Web, func(t *WebServerConfig) WebServerConfigView {
+		return t.View()
+	})
+}
+
+// A compilation failure here means this code must be regenerated, with the command at the top of this file.
+var _ServeConfigViewNeedsRegeneration = ServeConfig(struct {
+	TCP map[int]*TCPPortHandler
+	Web map[HostPort]*WebServerConfig
+}{})
+
+// View returns a readonly view of TCPPortHandler.
+func (p *TCPPortHandler) View() TCPPortHandlerView {
+	return TCPPortHandlerView{ж: p}
+}
+
+// TCPPortHandlerView provides a read-only view over TCPPortHandler.
+//
+// Its methods should only be called if `Valid()` returns true.
+type TCPPortHandlerView struct {
+	// ж is the underlying mutable value, named with a hard-to-type
+	// character that looks pointy like a pointer.
+	// It is named distinctively to make you think of how dangerous it is to escape
+	// to callers. You must not let callers be able to mutate it.
+	ж *TCPPortHandler
+}
+
+// Valid reports whether underlying value is non-nil.
+func (v TCPPortHandlerView) Valid() bool { return v.ж != nil }
+
+// AsStruct returns a clone of the underlying value which aliases no memory with
+// the original.
+func (v TCPPortHandlerView) AsStruct() *TCPPortHandler {
+	if v.ж == nil {
+		return nil
+	}
+	return v.ж.Clone()
+}
+
+func (v TCPPortHandlerView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+
+func (v *TCPPortHandlerView) UnmarshalJSON(b []byte) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	if len(b) == 0 {
+		return nil
+	}
+	var x TCPPortHandler
+	if err := json.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+func (v TCPPortHandlerView) HTTPS() bool        { return v.ж.HTTPS }
+func (v TCPPortHandlerView) TCPForward() string { return v.ж.TCPForward }
+func (v TCPPortHandlerView) TerminateTLS() bool { return v.ж.TerminateTLS }
+
+// A compilation failure here means this code must be regenerated, with the command at the top of this file.
+var _TCPPortHandlerViewNeedsRegeneration = TCPPortHandler(struct {
+	HTTPS        bool
+	TCPForward   string
+	TerminateTLS bool
+}{})
+
+// View returns a readonly view of HTTPHandler.
+func (p *HTTPHandler) View() HTTPHandlerView {
+	return HTTPHandlerView{ж: p}
+}
+
+// HTTPHandlerView provides a read-only view over HTTPHandler.
+//
+// Its methods should only be called if `Valid()` returns true.
+type HTTPHandlerView struct {
+	// ж is the underlying mutable value, named with a hard-to-type
+	// character that looks pointy like a pointer.
+	// It is named distinctively to make you think of how dangerous it is to escape
+	// to callers. You must not let callers be able to mutate it.
+	ж *HTTPHandler
+}
+
+// Valid reports whether underlying value is non-nil.
+func (v HTTPHandlerView) Valid() bool { return v.ж != nil }
+
+// AsStruct returns a clone of the underlying value which aliases no memory with
+// the original.
+func (v HTTPHandlerView) AsStruct() *HTTPHandler {
+	if v.ж == nil {
+		return nil
+	}
+	return v.ж.Clone()
+}
+
+func (v HTTPHandlerView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+
+func (v *HTTPHandlerView) UnmarshalJSON(b []byte) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	if len(b) == 0 {
+		return nil
+	}
+	var x HTTPHandler
+	if err := json.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+func (v HTTPHandlerView) Path() string  { return v.ж.Path }
+func (v HTTPHandlerView) Proxy() string { return v.ж.Proxy }
+
+// A compilation failure here means this code must be regenerated, with the command at the top of this file.
+var _HTTPHandlerViewNeedsRegeneration = HTTPHandler(struct {
+	Path  string
+	Proxy string
+}{})
+
+// View returns a readonly view of WebServerConfig.
+func (p *WebServerConfig) View() WebServerConfigView {
+	return WebServerConfigView{ж: p}
+}
+
+// WebServerConfigView provides a read-only view over WebServerConfig.
+//
+// Its methods should only be called if `Valid()` returns true.
+type WebServerConfigView struct {
+	// ж is the underlying mutable value, named with a hard-to-type
+	// character that looks pointy like a pointer.
+	// It is named distinctively to make you think of how dangerous it is to escape
+	// to callers. You must not let callers be able to mutate it.
+	ж *WebServerConfig
+}
+
+// Valid reports whether underlying value is non-nil.
+func (v WebServerConfigView) Valid() bool { return v.ж != nil }
+
+// AsStruct returns a clone of the underlying value which aliases no memory with
+// the original.
+func (v WebServerConfigView) AsStruct() *WebServerConfig {
+	if v.ж == nil {
+		return nil
+	}
+	return v.ж.Clone()
+}
+
+func (v WebServerConfigView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+
+func (v *WebServerConfigView) UnmarshalJSON(b []byte) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	if len(b) == 0 {
+		return nil
+	}
+	var x WebServerConfig
+	if err := json.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+func (v WebServerConfigView) Handlers() views.MapFn[string, *HTTPHandler, HTTPHandlerView] {
+	return views.MapFnOf(v.ж.Handlers, func(t *HTTPHandler) HTTPHandlerView {
+		return t.View()
+	})
+}
+
+// A compilation failure here means this code must be regenerated, with the command at the top of this file.
+var _WebServerConfigViewNeedsRegeneration = WebServerConfig(struct {
+	Handlers map[string]*HTTPHandler
+}{})

+ 2 - 2
ipn/ipnlocal/local.go

@@ -199,8 +199,8 @@ type LocalBackend struct {
 	componentLogUntil       map[string]componentLogState
 
 	// ServeConfig fields. (also guarded by mu)
-	lastServeConfJSON mem.RO // last JSON that was parsed into serveConfig
-	serveConfig       ipn.ServeConfig
+	lastServeConfJSON mem.RO          // last JSON that was parsed into serveConfig
+	serveConfig       ipn.ServeConfig // only replaced wholesale; don't mutate in-place
 
 	// statusLock must be held before calling statusChanged.Wait() or
 	// statusChanged.Broadcast().

+ 0 - 2
ipn/prefs.go

@@ -27,8 +27,6 @@ import (
 	"tailscale.com/util/dnsname"
 )
 
-//go:generate go run tailscale.com/cmd/viewer -type=Prefs
-
 // DefaultControlURL is the URL base of the control plane
 // ("coordination server") for use when no explicit one is configured.
 // The default control plane is the hosted version run by Tailscale.com.

+ 10 - 1
ipn/store.go

@@ -80,7 +80,16 @@ type ServeConfig struct {
 
 	// Web maps from "$SNI_NAME:$PORT" to a set of HTTP handlers
 	// keyed by mount point ("/", "/foo", etc)
-	Web map[string]map[string]*HTTPHandler `json:",omitempty"`
+	Web map[HostPort]*WebServerConfig `json:",omitempty"`
+}
+
+// HostPort is an SNI name and port number, joined by a colon.
+// There is no implicit port 443. It must contain a colon.
+type HostPort string
+
+// WebServerConfig describes a web server's configuration.
+type WebServerConfig struct {
+	Handlers map[string]*HTTPHandler
 }
 
 // TCPPortHandler describes what to do when handling a TCP