mirror of
				https://github.com/juanfont/headscale.git
				synced 2025-10-28 10:51:44 +01:00 
			
		
		
		
	Improvements on Noise implementation (#1379)
This commit is contained in:
		
							parent
							
								
									a2b760834f
								
							
						
					
					
						commit
						80772033ee
					
				@ -7,12 +7,13 @@
 | 
				
			|||||||
- Add environment flags to enable pprof (profiling) [#1382](https://github.com/juanfont/headscale/pull/1382)
 | 
					- Add environment flags to enable pprof (profiling) [#1382](https://github.com/juanfont/headscale/pull/1382)
 | 
				
			||||||
  - Profiles are continously generated in our integration tests.
 | 
					  - Profiles are continously generated in our integration tests.
 | 
				
			||||||
- Fix systemd service file location in `.deb` packages [#1391](https://github.com/juanfont/headscale/pull/1391)
 | 
					- Fix systemd service file location in `.deb` packages [#1391](https://github.com/juanfont/headscale/pull/1391)
 | 
				
			||||||
 | 
					- Improvements on Noise implementation [#1379](https://github.com/juanfont/headscale/pull/1379)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 0.22.1 (2023-04-20)
 | 
					## 0.22.1 (2023-04-20)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Changes
 | 
					### Changes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Fix issue where SystemD could not bind to port 80 [#1365](https://github.com/juanfont/headscale/pull/1365)
 | 
					- Fix issue where systemd could not bind to port 80 [#1365](https://github.com/juanfont/headscale/pull/1365)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 0.22.0 (2023-04-20)
 | 
					## 0.22.0 (2023-04-20)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										110
									
								
								noise.go
									
									
									
									
									
								
							
							
						
						
									
										110
									
								
								noise.go
									
									
									
									
									
								
							@ -1,6 +1,9 @@
 | 
				
			|||||||
package headscale
 | 
					package headscale
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/binary"
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/gorilla/mux"
 | 
						"github.com/gorilla/mux"
 | 
				
			||||||
@ -9,18 +12,37 @@ import (
 | 
				
			|||||||
	"golang.org/x/net/http2/h2c"
 | 
						"golang.org/x/net/http2/h2c"
 | 
				
			||||||
	"tailscale.com/control/controlbase"
 | 
						"tailscale.com/control/controlbase"
 | 
				
			||||||
	"tailscale.com/control/controlhttp"
 | 
						"tailscale.com/control/controlhttp"
 | 
				
			||||||
	"tailscale.com/net/netutil"
 | 
						"tailscale.com/tailcfg"
 | 
				
			||||||
 | 
						"tailscale.com/types/key"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
	// ts2021UpgradePath is the path that the server listens on for the WebSockets upgrade.
 | 
						// ts2021UpgradePath is the path that the server listens on for the WebSockets upgrade.
 | 
				
			||||||
	ts2021UpgradePath = "/ts2021"
 | 
						ts2021UpgradePath = "/ts2021"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// The first 9 bytes from the server to client over Noise are either an HTTP/2
 | 
				
			||||||
 | 
						// settings frame (a normal HTTP/2 setup) or, as Tailscale added later, an "early payload"
 | 
				
			||||||
 | 
						// header that's also 9 bytes long: 5 bytes (earlyPayloadMagic) followed by 4 bytes
 | 
				
			||||||
 | 
						// of length. Then that many bytes of JSON-encoded tailcfg.EarlyNoise.
 | 
				
			||||||
 | 
						// The early payload is optional. Some servers may not send it... But we do!
 | 
				
			||||||
 | 
						earlyPayloadMagic = "\xff\xff\xffTS"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// EarlyNoise was added in protocol version 49.
 | 
				
			||||||
 | 
						earlyNoiseCapabilityVersion = 49
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type ts2021App struct {
 | 
					type noiseServer struct {
 | 
				
			||||||
	headscale *Headscale
 | 
						headscale *Headscale
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						httpBaseConfig *http.Server
 | 
				
			||||||
 | 
						http2Server    *http2.Server
 | 
				
			||||||
	conn           *controlbase.Conn
 | 
						conn           *controlbase.Conn
 | 
				
			||||||
 | 
						machineKey     key.MachinePublic
 | 
				
			||||||
 | 
						nodeKey        key.NodePublic
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// EarlyNoise-related stuff
 | 
				
			||||||
 | 
						challenge       key.ChallengePrivate
 | 
				
			||||||
 | 
						protocolVersion int
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NoiseUpgradeHandler is to upgrade the connection and hijack the net.Conn
 | 
					// NoiseUpgradeHandler is to upgrade the connection and hijack the net.Conn
 | 
				
			||||||
@ -44,7 +66,18 @@ func (h *Headscale) NoiseUpgradeHandler(
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	noiseConn, err := controlhttp.AcceptHTTP(req.Context(), writer, req, *h.noisePrivateKey, nil)
 | 
						noiseServer := noiseServer{
 | 
				
			||||||
 | 
							headscale: h,
 | 
				
			||||||
 | 
							challenge: key.NewChallenge(),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						noiseConn, err := controlhttp.AcceptHTTP(
 | 
				
			||||||
 | 
							req.Context(),
 | 
				
			||||||
 | 
							writer,
 | 
				
			||||||
 | 
							req,
 | 
				
			||||||
 | 
							*h.noisePrivateKey,
 | 
				
			||||||
 | 
							noiseServer.earlyNoise,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Error().Err(err).Msg("noise upgrade failed")
 | 
							log.Error().Err(err).Msg("noise upgrade failed")
 | 
				
			||||||
		http.Error(writer, err.Error(), http.StatusInternalServerError)
 | 
							http.Error(writer, err.Error(), http.StatusInternalServerError)
 | 
				
			||||||
@ -52,10 +85,9 @@ func (h *Headscale) NoiseUpgradeHandler(
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ts2021App := ts2021App{
 | 
						noiseServer.conn = noiseConn
 | 
				
			||||||
		headscale: h,
 | 
						noiseServer.machineKey = noiseServer.conn.Peer()
 | 
				
			||||||
		conn:      noiseConn,
 | 
						noiseServer.protocolVersion = noiseServer.conn.ProtocolVersion()
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// This router is served only over the Noise connection, and exposes only the new API.
 | 
						// This router is served only over the Noise connection, and exposes only the new API.
 | 
				
			||||||
	//
 | 
						//
 | 
				
			||||||
@ -63,16 +95,70 @@ func (h *Headscale) NoiseUpgradeHandler(
 | 
				
			|||||||
	// a single hijacked connection from /ts2021, using netutil.NewOneConnListener
 | 
						// a single hijacked connection from /ts2021, using netutil.NewOneConnListener
 | 
				
			||||||
	router := mux.NewRouter()
 | 
						router := mux.NewRouter()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	router.HandleFunc("/machine/register", ts2021App.NoiseRegistrationHandler).
 | 
						router.HandleFunc("/machine/register", noiseServer.NoiseRegistrationHandler).
 | 
				
			||||||
		Methods(http.MethodPost)
 | 
							Methods(http.MethodPost)
 | 
				
			||||||
	router.HandleFunc("/machine/map", ts2021App.NoisePollNetMapHandler)
 | 
						router.HandleFunc("/machine/map", noiseServer.NoisePollNetMapHandler)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	server := http.Server{
 | 
						server := http.Server{
 | 
				
			||||||
		ReadTimeout: HTTPReadTimeout,
 | 
							ReadTimeout: HTTPReadTimeout,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	server.Handler = h2c.NewHandler(router, &http2.Server{})
 | 
					
 | 
				
			||||||
	err = server.Serve(netutil.NewOneConnListener(noiseConn, nil))
 | 
						noiseServer.httpBaseConfig = &http.Server{
 | 
				
			||||||
 | 
							Handler:           router,
 | 
				
			||||||
 | 
							ReadHeaderTimeout: HTTPReadTimeout,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						noiseServer.http2Server = &http2.Server{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						server.Handler = h2c.NewHandler(router, noiseServer.http2Server)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						noiseServer.http2Server.ServeConn(
 | 
				
			||||||
 | 
							noiseConn,
 | 
				
			||||||
 | 
							&http2.ServeConnOpts{
 | 
				
			||||||
 | 
								BaseConfig: noiseServer.httpBaseConfig,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (ns *noiseServer) earlyNoise(protocolVersion int, writer io.Writer) error {
 | 
				
			||||||
 | 
						log.Trace().
 | 
				
			||||||
 | 
							Caller().
 | 
				
			||||||
 | 
							Int("protocol_version", protocolVersion).
 | 
				
			||||||
 | 
							Str("challenge", ns.challenge.Public().String()).
 | 
				
			||||||
 | 
							Msg("earlyNoise called")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if protocolVersion < earlyNoiseCapabilityVersion {
 | 
				
			||||||
 | 
							log.Trace().
 | 
				
			||||||
 | 
								Caller().
 | 
				
			||||||
 | 
								Msgf("protocol version %d does not support early noise", protocolVersion)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						earlyJSON, err := json.Marshal(&tailcfg.EarlyNoise{
 | 
				
			||||||
 | 
							NodeKeyChallenge: ns.challenge.Public(),
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Info().Err(err).Msg("The HTTP2 server was closed")
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 5 bytes that won't be mistaken for an HTTP/2 frame:
 | 
				
			||||||
 | 
						// https://httpwg.org/specs/rfc7540.html#rfc.section.4.1 (Especially not
 | 
				
			||||||
 | 
						// an HTTP/2 settings frame, which isn't of type 'T')
 | 
				
			||||||
 | 
						var notH2Frame [5]byte
 | 
				
			||||||
 | 
						copy(notH2Frame[:], earlyPayloadMagic)
 | 
				
			||||||
 | 
						var lenBuf [4]byte
 | 
				
			||||||
 | 
						binary.BigEndian.PutUint32(lenBuf[:], uint32(len(earlyJSON)))
 | 
				
			||||||
 | 
						// These writes are all buffered by caller, so fine to do them
 | 
				
			||||||
 | 
						// separately:
 | 
				
			||||||
 | 
						if _, err := writer.Write(notH2Frame[:]); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if _, err := writer.Write(lenBuf[:]); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if _, err := writer.Write(earlyJSON); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -10,7 +10,7 @@ import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// // NoiseRegistrationHandler handles the actual registration process of a machine.
 | 
					// // NoiseRegistrationHandler handles the actual registration process of a machine.
 | 
				
			||||||
func (t *ts2021App) NoiseRegistrationHandler(
 | 
					func (ns *noiseServer) NoiseRegistrationHandler(
 | 
				
			||||||
	writer http.ResponseWriter,
 | 
						writer http.ResponseWriter,
 | 
				
			||||||
	req *http.Request,
 | 
						req *http.Request,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
@ -20,6 +20,11 @@ func (t *ts2021App) NoiseRegistrationHandler(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Trace().
 | 
				
			||||||
 | 
							Any("headers", req.Header).
 | 
				
			||||||
 | 
							Msg("Headers")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	body, _ := io.ReadAll(req.Body)
 | 
						body, _ := io.ReadAll(req.Body)
 | 
				
			||||||
	registerRequest := tailcfg.RegisterRequest{}
 | 
						registerRequest := tailcfg.RegisterRequest{}
 | 
				
			||||||
	if err := json.Unmarshal(body, ®isterRequest); err != nil {
 | 
						if err := json.Unmarshal(body, ®isterRequest); err != nil {
 | 
				
			||||||
@ -33,5 +38,7 @@ func (t *ts2021App) NoiseRegistrationHandler(
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.headscale.handleRegisterCommon(writer, req, registerRequest, t.conn.Peer(), true)
 | 
						ns.nodeKey = registerRequest.NodeKey
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ns.headscale.handleRegisterCommon(writer, req, registerRequest, ns.conn.Peer(), true)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -21,13 +21,18 @@ import (
 | 
				
			|||||||
// only after their first request (marked with the ReadOnly field).
 | 
					// only after their first request (marked with the ReadOnly field).
 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
// At this moment the updates are sent in a quite horrendous way, but they kinda work.
 | 
					// At this moment the updates are sent in a quite horrendous way, but they kinda work.
 | 
				
			||||||
func (t *ts2021App) NoisePollNetMapHandler(
 | 
					func (ns *noiseServer) NoisePollNetMapHandler(
 | 
				
			||||||
	writer http.ResponseWriter,
 | 
						writer http.ResponseWriter,
 | 
				
			||||||
	req *http.Request,
 | 
						req *http.Request,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
	log.Trace().
 | 
						log.Trace().
 | 
				
			||||||
		Str("handler", "NoisePollNetMap").
 | 
							Str("handler", "NoisePollNetMap").
 | 
				
			||||||
		Msg("PollNetMapHandler called")
 | 
							Msg("PollNetMapHandler called")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Trace().
 | 
				
			||||||
 | 
							Any("headers", req.Header).
 | 
				
			||||||
 | 
							Msg("Headers")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	body, _ := io.ReadAll(req.Body)
 | 
						body, _ := io.ReadAll(req.Body)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mapRequest := tailcfg.MapRequest{}
 | 
						mapRequest := tailcfg.MapRequest{}
 | 
				
			||||||
@ -41,7 +46,9 @@ func (t *ts2021App) NoisePollNetMapHandler(
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	machine, err := t.headscale.GetMachineByAnyKey(t.conn.Peer(), mapRequest.NodeKey, key.NodePublic{})
 | 
						ns.nodeKey = mapRequest.NodeKey
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						machine, err := ns.headscale.GetMachineByAnyKey(ns.conn.Peer(), mapRequest.NodeKey, key.NodePublic{})
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		if errors.Is(err, gorm.ErrRecordNotFound) {
 | 
							if errors.Is(err, gorm.ErrRecordNotFound) {
 | 
				
			||||||
			log.Warn().
 | 
								log.Warn().
 | 
				
			||||||
@ -63,5 +70,5 @@ func (t *ts2021App) NoisePollNetMapHandler(
 | 
				
			|||||||
		Str("machine", machine.Hostname).
 | 
							Str("machine", machine.Hostname).
 | 
				
			||||||
		Msg("A machine is entering polling via the Noise protocol")
 | 
							Msg("A machine is entering polling via the Noise protocol")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.headscale.handlePollCommon(writer, req.Context(), machine, mapRequest, true)
 | 
						ns.headscale.handlePollCommon(writer, req.Context(), machine, mapRequest, true)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user