mirror of
				https://github.com/juanfont/headscale.git
				synced 2025-10-28 10:51:44 +01:00 
			
		
		
		
	Merged main
This commit is contained in:
		
						commit
						2eef535b4b
					
				
							
								
								
									
										43
									
								
								api.go
									
									
									
									
									
								
							
							
						
						
									
										43
									
								
								api.go
									
									
									
									
									
								
							@ -64,6 +64,7 @@ func (h *Headscale) RegistrationHandler(c *gin.Context) {
 | 
				
			|||||||
			Str("handler", "Registration").
 | 
								Str("handler", "Registration").
 | 
				
			||||||
			Err(err).
 | 
								Err(err).
 | 
				
			||||||
			Msg("Cannot parse machine key")
 | 
								Msg("Cannot parse machine key")
 | 
				
			||||||
 | 
							machineRegistrations.WithLabelValues("unkown", "web", "error", "unknown").Inc()
 | 
				
			||||||
		c.String(http.StatusInternalServerError, "Sad!")
 | 
							c.String(http.StatusInternalServerError, "Sad!")
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -74,6 +75,7 @@ func (h *Headscale) RegistrationHandler(c *gin.Context) {
 | 
				
			|||||||
			Str("handler", "Registration").
 | 
								Str("handler", "Registration").
 | 
				
			||||||
			Err(err).
 | 
								Err(err).
 | 
				
			||||||
			Msg("Cannot decode message")
 | 
								Msg("Cannot decode message")
 | 
				
			||||||
 | 
							machineRegistrations.WithLabelValues("unkown", "web", "error", "unknown").Inc()
 | 
				
			||||||
		c.String(http.StatusInternalServerError, "Very sad!")
 | 
							c.String(http.StatusInternalServerError, "Very sad!")
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -94,6 +96,7 @@ func (h *Headscale) RegistrationHandler(c *gin.Context) {
 | 
				
			|||||||
				Str("handler", "Registration").
 | 
									Str("handler", "Registration").
 | 
				
			||||||
				Err(err).
 | 
									Err(err).
 | 
				
			||||||
				Msg("Could not create row")
 | 
									Msg("Could not create row")
 | 
				
			||||||
 | 
								machineRegistrations.WithLabelValues("unkown", "web", "error", m.Namespace.Name).Inc()
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -122,9 +125,11 @@ func (h *Headscale) RegistrationHandler(c *gin.Context) {
 | 
				
			|||||||
					Str("handler", "Registration").
 | 
										Str("handler", "Registration").
 | 
				
			||||||
					Err(err).
 | 
										Err(err).
 | 
				
			||||||
					Msg("Cannot encode message")
 | 
										Msg("Cannot encode message")
 | 
				
			||||||
 | 
									machineRegistrations.WithLabelValues("update", "web", "error", m.Namespace.Name).Inc()
 | 
				
			||||||
				c.String(http.StatusInternalServerError, "")
 | 
									c.String(http.StatusInternalServerError, "")
 | 
				
			||||||
				return
 | 
									return
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
								machineRegistrations.WithLabelValues("update", "web", "success", m.Namespace.Name).Inc()
 | 
				
			||||||
			c.Data(200, "application/json; charset=utf-8", respBody)
 | 
								c.Data(200, "application/json; charset=utf-8", respBody)
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@ -141,9 +146,11 @@ func (h *Headscale) RegistrationHandler(c *gin.Context) {
 | 
				
			|||||||
				Str("handler", "Registration").
 | 
									Str("handler", "Registration").
 | 
				
			||||||
				Err(err).
 | 
									Err(err).
 | 
				
			||||||
				Msg("Cannot encode message")
 | 
									Msg("Cannot encode message")
 | 
				
			||||||
 | 
								machineRegistrations.WithLabelValues("new", "web", "error", m.Namespace.Name).Inc()
 | 
				
			||||||
			c.String(http.StatusInternalServerError, "")
 | 
								c.String(http.StatusInternalServerError, "")
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							machineRegistrations.WithLabelValues("new", "web", "success", m.Namespace.Name).Inc()
 | 
				
			||||||
		c.Data(200, "application/json; charset=utf-8", respBody)
 | 
							c.Data(200, "application/json; charset=utf-8", respBody)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -213,12 +220,12 @@ func (h *Headscale) RegistrationHandler(c *gin.Context) {
 | 
				
			|||||||
	c.Data(200, "application/json; charset=utf-8", respBody)
 | 
						c.Data(200, "application/json; charset=utf-8", respBody)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (h *Headscale) getMapResponse(mKey wgkey.Key, req tailcfg.MapRequest, m Machine) (*[]byte, error) {
 | 
					func (h *Headscale) getMapResponse(mKey wgkey.Key, req tailcfg.MapRequest, m *Machine) ([]byte, error) {
 | 
				
			||||||
	log.Trace().
 | 
						log.Trace().
 | 
				
			||||||
		Str("func", "getMapResponse").
 | 
							Str("func", "getMapResponse").
 | 
				
			||||||
		Str("machine", req.Hostinfo.Hostname).
 | 
							Str("machine", req.Hostinfo.Hostname).
 | 
				
			||||||
		Msg("Creating Map response")
 | 
							Msg("Creating Map response")
 | 
				
			||||||
	node, err := h.toNode(m, true)
 | 
						node, err := m.toNode(true)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Error().
 | 
							log.Error().
 | 
				
			||||||
			Str("func", "getMapResponse").
 | 
								Str("func", "getMapResponse").
 | 
				
			||||||
@ -226,6 +233,7 @@ func (h *Headscale) getMapResponse(mKey wgkey.Key, req tailcfg.MapRequest, m Mac
 | 
				
			|||||||
			Msg("Cannot convert to node")
 | 
								Msg("Cannot convert to node")
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	peers, err := h.getPeers(m)
 | 
						peers, err := h.getPeers(m)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Error().
 | 
							log.Error().
 | 
				
			||||||
@ -241,6 +249,15 @@ func (h *Headscale) getMapResponse(mKey wgkey.Key, req tailcfg.MapRequest, m Mac
 | 
				
			|||||||
		DisplayName: m.Namespace.Name,
 | 
							DisplayName: m.Namespace.Name,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						nodePeers, err := peers.toNodes(true)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Error().
 | 
				
			||||||
 | 
								Str("func", "getMapResponse").
 | 
				
			||||||
 | 
								Err(err).
 | 
				
			||||||
 | 
								Msg("Failed to convert peers to Tailscale nodes")
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var dnsConfig *tailcfg.DNSConfig
 | 
						var dnsConfig *tailcfg.DNSConfig
 | 
				
			||||||
	if h.cfg.DNSConfig != nil && h.cfg.DNSConfig.Proxied { // if MagicDNS is enabled
 | 
						if h.cfg.DNSConfig != nil && h.cfg.DNSConfig.Proxied { // if MagicDNS is enabled
 | 
				
			||||||
		// TODO(juanfont): We should not be regenerating this all the time
 | 
							// TODO(juanfont): We should not be regenerating this all the time
 | 
				
			||||||
@ -260,16 +277,19 @@ func (h *Headscale) getMapResponse(mKey wgkey.Key, req tailcfg.MapRequest, m Mac
 | 
				
			|||||||
	resp := tailcfg.MapResponse{
 | 
						resp := tailcfg.MapResponse{
 | 
				
			||||||
		KeepAlive:    false,
 | 
							KeepAlive:    false,
 | 
				
			||||||
		Node:         node,
 | 
							Node:         node,
 | 
				
			||||||
		Peers:        *peers,
 | 
							Peers:        nodePeers,
 | 
				
			||||||
		DNSConfig:    dnsConfig,
 | 
							DNSConfig:    dnsConfig,
 | 
				
			||||||
		Domain:       h.cfg.BaseDomain,
 | 
							Domain:       h.cfg.BaseDomain,
 | 
				
			||||||
		PacketFilter: *h.aclRules,
 | 
							PacketFilter: *h.aclRules,
 | 
				
			||||||
		DERPMap:      h.cfg.DerpMap,
 | 
							DERPMap:      h.cfg.DerpMap,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// TODO(juanfont): We should send the profiles of all the peers (this own namespace + those from the shared peers)
 | 
				
			||||||
		UserProfiles: []tailcfg.UserProfile{profile},
 | 
							UserProfiles: []tailcfg.UserProfile{profile},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	log.Trace().
 | 
						log.Trace().
 | 
				
			||||||
		Str("func", "getMapResponse").
 | 
							Str("func", "getMapResponse").
 | 
				
			||||||
		Str("machine", req.Hostinfo.Hostname).
 | 
							Str("machine", req.Hostinfo.Hostname).
 | 
				
			||||||
 | 
							Interface("payload", resp).
 | 
				
			||||||
		Msgf("Generated map response: %s", tailMapResponseToString(resp))
 | 
							Msgf("Generated map response: %s", tailMapResponseToString(resp))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var respBody []byte
 | 
						var respBody []byte
 | 
				
			||||||
@ -292,10 +312,10 @@ func (h *Headscale) getMapResponse(mKey wgkey.Key, req tailcfg.MapRequest, m Mac
 | 
				
			|||||||
	data := make([]byte, 4)
 | 
						data := make([]byte, 4)
 | 
				
			||||||
	binary.LittleEndian.PutUint32(data, uint32(len(respBody)))
 | 
						binary.LittleEndian.PutUint32(data, uint32(len(respBody)))
 | 
				
			||||||
	data = append(data, respBody...)
 | 
						data = append(data, respBody...)
 | 
				
			||||||
	return &data, nil
 | 
						return data, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (h *Headscale) getMapKeepAliveResponse(mKey wgkey.Key, req tailcfg.MapRequest, m Machine) (*[]byte, error) {
 | 
					func (h *Headscale) getMapKeepAliveResponse(mKey wgkey.Key, req tailcfg.MapRequest, m *Machine) ([]byte, error) {
 | 
				
			||||||
	resp := tailcfg.MapResponse{
 | 
						resp := tailcfg.MapResponse{
 | 
				
			||||||
		KeepAlive: true,
 | 
							KeepAlive: true,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -318,7 +338,7 @@ func (h *Headscale) getMapKeepAliveResponse(mKey wgkey.Key, req tailcfg.MapReque
 | 
				
			|||||||
	data := make([]byte, 4)
 | 
						data := make([]byte, 4)
 | 
				
			||||||
	binary.LittleEndian.PutUint32(data, uint32(len(respBody)))
 | 
						binary.LittleEndian.PutUint32(data, uint32(len(respBody)))
 | 
				
			||||||
	data = append(data, respBody...)
 | 
						data = append(data, respBody...)
 | 
				
			||||||
	return &data, nil
 | 
						return data, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (h *Headscale) handleAuthKey(c *gin.Context, db *gorm.DB, idKey wgkey.Key, req tailcfg.RegisterRequest, m Machine) {
 | 
					func (h *Headscale) handleAuthKey(c *gin.Context, db *gorm.DB, idKey wgkey.Key, req tailcfg.RegisterRequest, m Machine) {
 | 
				
			||||||
@ -343,9 +363,15 @@ func (h *Headscale) handleAuthKey(c *gin.Context, db *gorm.DB, idKey wgkey.Key,
 | 
				
			|||||||
				Err(err).
 | 
									Err(err).
 | 
				
			||||||
				Msg("Cannot encode message")
 | 
									Msg("Cannot encode message")
 | 
				
			||||||
			c.String(http.StatusInternalServerError, "")
 | 
								c.String(http.StatusInternalServerError, "")
 | 
				
			||||||
 | 
								machineRegistrations.WithLabelValues("new", "authkey", "error", m.Namespace.Name).Inc()
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		c.Data(200, "application/json; charset=utf-8", respBody)
 | 
							c.Data(401, "application/json; charset=utf-8", respBody)
 | 
				
			||||||
 | 
							log.Error().
 | 
				
			||||||
 | 
								Str("func", "handleAuthKey").
 | 
				
			||||||
 | 
								Str("machine", m.Name).
 | 
				
			||||||
 | 
								Msg("Failed authentication via AuthKey")
 | 
				
			||||||
 | 
							machineRegistrations.WithLabelValues("new", "authkey", "error", m.Namespace.Name).Inc()
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -359,6 +385,7 @@ func (h *Headscale) handleAuthKey(c *gin.Context, db *gorm.DB, idKey wgkey.Key,
 | 
				
			|||||||
			Str("func", "handleAuthKey").
 | 
								Str("func", "handleAuthKey").
 | 
				
			||||||
			Str("machine", m.Name).
 | 
								Str("machine", m.Name).
 | 
				
			||||||
			Msg("Failed to find an available IP")
 | 
								Msg("Failed to find an available IP")
 | 
				
			||||||
 | 
							machineRegistrations.WithLabelValues("new", "authkey", "error", m.Namespace.Name).Inc()
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	log.Info().
 | 
						log.Info().
 | 
				
			||||||
@ -384,9 +411,11 @@ func (h *Headscale) handleAuthKey(c *gin.Context, db *gorm.DB, idKey wgkey.Key,
 | 
				
			|||||||
			Str("machine", m.Name).
 | 
								Str("machine", m.Name).
 | 
				
			||||||
			Err(err).
 | 
								Err(err).
 | 
				
			||||||
			Msg("Cannot encode message")
 | 
								Msg("Cannot encode message")
 | 
				
			||||||
 | 
							machineRegistrations.WithLabelValues("new", "authkey", "error", m.Namespace.Name).Inc()
 | 
				
			||||||
		c.String(http.StatusInternalServerError, "Extremely sad!")
 | 
							c.String(http.StatusInternalServerError, "Extremely sad!")
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						machineRegistrations.WithLabelValues("new", "authkey", "success", m.Namespace.Name).Inc()
 | 
				
			||||||
	c.Data(200, "application/json; charset=utf-8", respBody)
 | 
						c.Data(200, "application/json; charset=utf-8", respBody)
 | 
				
			||||||
	log.Info().
 | 
						log.Info().
 | 
				
			||||||
		Str("func", "handleAuthKey").
 | 
							Str("func", "handleAuthKey").
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										7
									
								
								app.go
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								app.go
									
									
									
									
									
								
							@ -12,6 +12,7 @@ import (
 | 
				
			|||||||
	"github.com/rs/zerolog/log"
 | 
						"github.com/rs/zerolog/log"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/gin-gonic/gin"
 | 
						"github.com/gin-gonic/gin"
 | 
				
			||||||
 | 
						"github.com/zsais/go-gin-prometheus"
 | 
				
			||||||
	"golang.org/x/crypto/acme"
 | 
						"golang.org/x/crypto/acme"
 | 
				
			||||||
	"golang.org/x/crypto/acme/autocert"
 | 
						"golang.org/x/crypto/acme/autocert"
 | 
				
			||||||
	"gorm.io/gorm"
 | 
						"gorm.io/gorm"
 | 
				
			||||||
@ -157,6 +158,7 @@ func (h *Headscale) expireEphemeralNodesWorker() {
 | 
				
			|||||||
				if err != nil {
 | 
									if err != nil {
 | 
				
			||||||
					log.Error().Err(err).Str("machine", m.Name).Msg("🤮 Cannot delete ephemeral machine from the database")
 | 
										log.Error().Err(err).Str("machine", m.Name).Msg("🤮 Cannot delete ephemeral machine from the database")
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
									updateRequestsFromNode.WithLabelValues("ephemeral-node-update").Inc()
 | 
				
			||||||
				h.notifyChangesToPeers(&m)
 | 
									h.notifyChangesToPeers(&m)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@ -180,6 +182,10 @@ func (h *Headscale) watchForKVUpdatesWorker() {
 | 
				
			|||||||
// Serve launches a GIN server with the Headscale API
 | 
					// Serve launches a GIN server with the Headscale API
 | 
				
			||||||
func (h *Headscale) Serve() error {
 | 
					func (h *Headscale) Serve() error {
 | 
				
			||||||
	r := gin.Default()
 | 
						r := gin.Default()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						p := ginprometheus.NewPrometheus("gin")
 | 
				
			||||||
 | 
						p.Use(r)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	r.GET("/health", func(c *gin.Context) { c.JSON(200, gin.H{"healthy": "ok"}) })
 | 
						r.GET("/health", func(c *gin.Context) { c.JSON(200, gin.H{"healthy": "ok"}) })
 | 
				
			||||||
	r.GET("/key", h.KeyHandler)
 | 
						r.GET("/key", h.KeyHandler)
 | 
				
			||||||
	r.GET("/register", h.RegisterWebAPI)
 | 
						r.GET("/register", h.RegisterWebAPI)
 | 
				
			||||||
@ -254,6 +260,7 @@ func (h *Headscale) Serve() error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func (h *Headscale) setLastStateChangeToNow(namespace string) {
 | 
					func (h *Headscale) setLastStateChangeToNow(namespace string) {
 | 
				
			||||||
	now := time.Now().UTC()
 | 
						now := time.Now().UTC()
 | 
				
			||||||
 | 
						lastStateUpdate.WithLabelValues("", "headscale").Set(float64(now.Unix()))
 | 
				
			||||||
	h.lastStateChange.Store(namespace, now)
 | 
						h.lastStateChange.Store(namespace, now)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										2
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.mod
									
									
									
									
									
								
							@ -20,6 +20,7 @@ require (
 | 
				
			|||||||
	github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
 | 
						github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
 | 
				
			||||||
	github.com/opencontainers/runc v1.0.2 // indirect
 | 
						github.com/opencontainers/runc v1.0.2 // indirect
 | 
				
			||||||
	github.com/ory/dockertest/v3 v3.7.0
 | 
						github.com/ory/dockertest/v3 v3.7.0
 | 
				
			||||||
 | 
						github.com/prometheus/client_golang v1.11.0 // indirect
 | 
				
			||||||
	github.com/pterm/pterm v0.12.30
 | 
						github.com/pterm/pterm v0.12.30
 | 
				
			||||||
	github.com/rs/zerolog v1.25.0
 | 
						github.com/rs/zerolog v1.25.0
 | 
				
			||||||
	github.com/spf13/cobra v1.2.1
 | 
						github.com/spf13/cobra v1.2.1
 | 
				
			||||||
@ -28,6 +29,7 @@ require (
 | 
				
			|||||||
	github.com/tailscale/hujson v0.0.0-20210818175511-7360507a6e88
 | 
						github.com/tailscale/hujson v0.0.0-20210818175511-7360507a6e88
 | 
				
			||||||
	github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e
 | 
						github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e
 | 
				
			||||||
	github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
 | 
						github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
 | 
				
			||||||
 | 
						github.com/zsais/go-gin-prometheus v0.1.0 // indirect
 | 
				
			||||||
	golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
 | 
						golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
 | 
				
			||||||
	golang.org/x/net v0.0.0-20210913180222-943fd674d43e // indirect
 | 
						golang.org/x/net v0.0.0-20210913180222-943fd674d43e // indirect
 | 
				
			||||||
	golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0 // indirect
 | 
						golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0 // indirect
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										26
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								go.sum
									
									
									
									
									
								
							@ -103,6 +103,7 @@ github.com/aws/aws-sdk-go v1.38.52/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2z
 | 
				
			|||||||
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
 | 
					github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
 | 
				
			||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 | 
					github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 | 
				
			||||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 | 
					github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 | 
				
			||||||
 | 
					github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 | 
				
			||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 | 
					github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 | 
				
			||||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
 | 
					github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
 | 
				
			||||||
github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
 | 
					github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
 | 
				
			||||||
@ -118,7 +119,9 @@ github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInq
 | 
				
			|||||||
github.com/cenkalti/backoff/v4 v4.1.1 h1:G2HAfAmvm/GcKan2oOQpBXOd2tT2G57ZnZGWa1PxPBQ=
 | 
					github.com/cenkalti/backoff/v4 v4.1.1 h1:G2HAfAmvm/GcKan2oOQpBXOd2tT2G57ZnZGWa1PxPBQ=
 | 
				
			||||||
github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
 | 
					github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
 | 
				
			||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 | 
					github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 | 
				
			||||||
 | 
					github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
 | 
				
			||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 | 
					github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 | 
				
			||||||
 | 
					github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
 | 
				
			||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 | 
					github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 | 
				
			||||||
github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M=
 | 
					github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M=
 | 
				
			||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 | 
					github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 | 
				
			||||||
@ -518,6 +521,7 @@ github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhB
 | 
				
			|||||||
github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
 | 
					github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
 | 
				
			||||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 | 
					github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 | 
				
			||||||
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
 | 
					github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
 | 
				
			||||||
 | 
					github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
 | 
				
			||||||
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
 | 
					github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
 | 
				
			||||||
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
 | 
					github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
 | 
				
			||||||
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
 | 
					github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
 | 
				
			||||||
@ -531,6 +535,7 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV
 | 
				
			|||||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 | 
					github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 | 
				
			||||||
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 | 
					github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 | 
				
			||||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 | 
					github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 | 
				
			||||||
 | 
					github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 | 
				
			||||||
github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ=
 | 
					github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ=
 | 
				
			||||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 | 
					github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 | 
				
			||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 | 
					github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 | 
				
			||||||
@ -538,6 +543,7 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X
 | 
				
			|||||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
 | 
					github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
 | 
				
			||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 | 
					github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 | 
				
			||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 | 
					github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 | 
				
			||||||
 | 
					github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
 | 
				
			||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
 | 
					github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
 | 
				
			||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
 | 
					github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
 | 
				
			||||||
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
 | 
					github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
 | 
				
			||||||
@ -616,6 +622,7 @@ github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGw
 | 
				
			|||||||
github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU=
 | 
					github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU=
 | 
				
			||||||
github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 | 
					github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 | 
				
			||||||
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
 | 
					github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
 | 
				
			||||||
 | 
					github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
 | 
				
			||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 | 
					github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 | 
				
			||||||
github.com/mbilski/exhaustivestruct v1.1.0/go.mod h1:OeTBVxQWoEmB2J2JCHmXWPJ0aksxSUOUy+nvtVEfzXc=
 | 
					github.com/mbilski/exhaustivestruct v1.1.0/go.mod h1:OeTBVxQWoEmB2J2JCHmXWPJ0aksxSUOUy+nvtVEfzXc=
 | 
				
			||||||
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y=
 | 
					github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y=
 | 
				
			||||||
@ -669,6 +676,7 @@ github.com/moricho/tparallel v0.2.1/go.mod h1:fXEIZxG2vdfl0ZF8b42f5a78EhjjD5mX8q
 | 
				
			|||||||
github.com/mozilla/tls-observatory v0.0.0-20200317151703-4fa42e1c2dee/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk=
 | 
					github.com/mozilla/tls-observatory v0.0.0-20200317151703-4fa42e1c2dee/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk=
 | 
				
			||||||
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
 | 
					github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
 | 
				
			||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 | 
					github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 | 
				
			||||||
 | 
					github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 | 
				
			||||||
github.com/nakabonne/nestif v0.3.0/go.mod h1:dI314BppzXjJ4HsCnbo7XzrJHPszZsjnk5wEBSYHI2c=
 | 
					github.com/nakabonne/nestif v0.3.0/go.mod h1:dI314BppzXjJ4HsCnbo7XzrJHPszZsjnk5wEBSYHI2c=
 | 
				
			||||||
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
 | 
					github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
 | 
				
			||||||
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
 | 
					github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
 | 
				
			||||||
@ -747,21 +755,32 @@ github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod
 | 
				
			|||||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
 | 
					github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
 | 
				
			||||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
 | 
					github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
 | 
				
			||||||
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
 | 
					github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
 | 
				
			||||||
 | 
					github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
 | 
				
			||||||
 | 
					github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ=
 | 
				
			||||||
 | 
					github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
 | 
				
			||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 | 
					github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 | 
				
			||||||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 | 
					github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 | 
				
			||||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 | 
					github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 | 
				
			||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 | 
					github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 | 
				
			||||||
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 | 
					github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 | 
				
			||||||
 | 
					github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
 | 
				
			||||||
 | 
					github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 | 
				
			||||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
 | 
					github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
 | 
				
			||||||
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 | 
					github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 | 
				
			||||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 | 
					github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 | 
				
			||||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 | 
					github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 | 
				
			||||||
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
 | 
					github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
 | 
				
			||||||
 | 
					github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
 | 
				
			||||||
 | 
					github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ=
 | 
				
			||||||
 | 
					github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
 | 
				
			||||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 | 
					github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 | 
				
			||||||
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 | 
					github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 | 
				
			||||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 | 
					github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 | 
				
			||||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 | 
					github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 | 
				
			||||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
 | 
					github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
 | 
				
			||||||
 | 
					github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
 | 
				
			||||||
 | 
					github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
 | 
				
			||||||
 | 
					github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
 | 
				
			||||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
 | 
					github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
 | 
				
			||||||
github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI=
 | 
					github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI=
 | 
				
			||||||
github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY1U7lg=
 | 
					github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY1U7lg=
 | 
				
			||||||
@ -929,6 +948,8 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
 | 
				
			|||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 | 
					github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 | 
				
			||||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
 | 
					github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
 | 
				
			||||||
github.com/ziutek/telnet v0.0.0-20180329124119-c3b780dc415b/go.mod h1:IZpXDfkJ6tWD3PhBK5YzgQT+xJWh7OsdwiG8hA2MkO4=
 | 
					github.com/ziutek/telnet v0.0.0-20180329124119-c3b780dc415b/go.mod h1:IZpXDfkJ6tWD3PhBK5YzgQT+xJWh7OsdwiG8hA2MkO4=
 | 
				
			||||||
 | 
					github.com/zsais/go-gin-prometheus v0.1.0 h1:bkLv1XCdzqVgQ36ScgRi09MA2UC1t3tAB6nsfErsGO4=
 | 
				
			||||||
 | 
					github.com/zsais/go-gin-prometheus v0.1.0/go.mod h1:Slirjzuz8uM8Cw0jmPNqbneoqcUtY2GGjn2bEd4NRLY=
 | 
				
			||||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 | 
					go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 | 
				
			||||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 | 
					go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 | 
				
			||||||
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
 | 
					go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
 | 
				
			||||||
@ -1143,6 +1164,7 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w
 | 
				
			|||||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
					golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
					golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
					golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
					golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
					golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
					golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
@ -1158,6 +1180,8 @@ golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7w
 | 
				
			|||||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
					golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
					golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
					golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
					golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
					golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
					golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
@ -1190,6 +1214,7 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
 | 
				
			|||||||
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
					golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
					golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
					golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
					golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
					golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
					golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
@ -1450,6 +1475,7 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
				
			|||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
					gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
				
			||||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
					gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
				
			||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
					gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
				
			||||||
 | 
					gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
				
			||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
					gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
				
			||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
					gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
				
			||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 | 
					gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 | 
				
			||||||
 | 
				
			|||||||
@ -7,6 +7,7 @@ import (
 | 
				
			|||||||
	"bytes"
 | 
						"bytes"
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"io/ioutil"
 | 
						"io/ioutil"
 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
@ -233,9 +234,6 @@ func (s *IntegrationTestSuite) SetupSuite() {
 | 
				
			|||||||
		},
 | 
							},
 | 
				
			||||||
		Networks: []*dockertest.Network{&network},
 | 
							Networks: []*dockertest.Network{&network},
 | 
				
			||||||
		Cmd:      []string{"headscale", "serve"},
 | 
							Cmd:      []string{"headscale", "serve"},
 | 
				
			||||||
		PortBindings: map[docker.Port][]docker.PortBinding{
 | 
					 | 
				
			||||||
			"8080/tcp": {{HostPort: "8080"}},
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	fmt.Println("Creating headscale container")
 | 
						fmt.Println("Creating headscale container")
 | 
				
			||||||
@ -270,7 +268,11 @@ func (s *IntegrationTestSuite) SetupSuite() {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	}); err != nil {
 | 
						}); err != nil {
 | 
				
			||||||
		log.Fatalf("Could not connect to docker: %s", err)
 | 
							// TODO(kradalby): If we cannot access headscale, or any other fatal error during
 | 
				
			||||||
 | 
							// test setup, we need to abort and tear down. However, testify does not seem to
 | 
				
			||||||
 | 
							// support that at the moment:
 | 
				
			||||||
 | 
							// https://github.com/stretchr/testify/issues/849
 | 
				
			||||||
 | 
							return // fmt.Errorf("Could not connect to headscale: %s", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	fmt.Println("headscale container is ready")
 | 
						fmt.Println("headscale container is ready")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -292,7 +294,7 @@ func (s *IntegrationTestSuite) SetupSuite() {
 | 
				
			|||||||
		)
 | 
							)
 | 
				
			||||||
		assert.Nil(s.T(), err)
 | 
							assert.Nil(s.T(), err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		headscaleEndpoint := fmt.Sprintf("http://headscale:%s", headscale.GetPort("8080/tcp"))
 | 
							headscaleEndpoint := "http://headscale:8080"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		fmt.Printf("Joining tailscale containers to headscale at %s\n", headscaleEndpoint)
 | 
							fmt.Printf("Joining tailscale containers to headscale at %s\n", headscaleEndpoint)
 | 
				
			||||||
		for hostname, tailscale := range scales.tailscales {
 | 
							for hostname, tailscale := range scales.tailscales {
 | 
				
			||||||
@ -353,15 +355,16 @@ func (s *IntegrationTestSuite) TestGetIpAddresses() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		for hostname := range scales.tailscales {
 | 
							for hostname := range scales.tailscales {
 | 
				
			||||||
			s.T().Run(hostname, func(t *testing.T) {
 | 
								s.T().Run(hostname, func(t *testing.T) {
 | 
				
			||||||
				ip := ips[hostname]
 | 
									ip, ok := ips[hostname]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									assert.True(t, ok)
 | 
				
			||||||
 | 
									assert.NotNil(t, ip)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				fmt.Printf("IP for %s: %s\n", hostname, ip)
 | 
									fmt.Printf("IP for %s: %s\n", hostname, ip)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				// c.Assert(ip.Valid(), check.IsTrue)
 | 
									// c.Assert(ip.Valid(), check.IsTrue)
 | 
				
			||||||
				assert.True(t, ip.Is4())
 | 
									assert.True(t, ip.Is4())
 | 
				
			||||||
				assert.True(t, ipPrefix.Contains(ip))
 | 
									assert.True(t, ipPrefix.Contains(ip))
 | 
				
			||||||
 | 
					 | 
				
			||||||
				ips[hostname] = ip
 | 
					 | 
				
			||||||
			})
 | 
								})
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -724,6 +727,9 @@ func getAPIURLs(tailscales map[string]dockertest.Resource) (map[netaddr.IP]strin
 | 
				
			|||||||
			n := ft.Node
 | 
								n := ft.Node
 | 
				
			||||||
			for _, a := range n.Addresses { // just add all the addresses
 | 
								for _, a := range n.Addresses { // just add all the addresses
 | 
				
			||||||
				if _, ok := fts[a.IP()]; !ok {
 | 
									if _, ok := fts[a.IP()]; !ok {
 | 
				
			||||||
 | 
										if ft.PeerAPIURL == "" {
 | 
				
			||||||
 | 
											return nil, errors.New("api url is empty")
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
					fts[a.IP()] = ft.PeerAPIURL
 | 
										fts[a.IP()] = ft.PeerAPIURL
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
				
			|||||||
@ -7,7 +7,7 @@
 | 
				
			|||||||
  "db_type": "sqlite3",
 | 
					  "db_type": "sqlite3",
 | 
				
			||||||
  "db_path": "/tmp/integration_test_db.sqlite3",
 | 
					  "db_path": "/tmp/integration_test_db.sqlite3",
 | 
				
			||||||
  "acl_policy_path": "",
 | 
					  "acl_policy_path": "",
 | 
				
			||||||
  "log_level": "debug",
 | 
					  "log_level": "trace",
 | 
				
			||||||
  "dns_config": {
 | 
					  "dns_config": {
 | 
				
			||||||
    "nameservers": [
 | 
					    "nameservers": [
 | 
				
			||||||
      "1.1.1.1"
 | 
					      "1.1.1.1"
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										572
									
								
								machine.go
									
									
									
									
									
								
							
							
						
						
									
										572
									
								
								machine.go
									
									
									
									
									
								
							@ -6,6 +6,7 @@ import (
 | 
				
			|||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"sort"
 | 
						"sort"
 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/rs/zerolog/log"
 | 
						"github.com/rs/zerolog/log"
 | 
				
			||||||
@ -45,14 +46,338 @@ type Machine struct {
 | 
				
			|||||||
	DeletedAt *time.Time
 | 
						DeletedAt *time.Time
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type (
 | 
				
			||||||
 | 
						Machines  []Machine
 | 
				
			||||||
 | 
						MachinesP []*Machine
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// For the time being this method is rather naive
 | 
					// For the time being this method is rather naive
 | 
				
			||||||
func (m Machine) isAlreadyRegistered() bool {
 | 
					func (m Machine) isAlreadyRegistered() bool {
 | 
				
			||||||
	return m.Registered
 | 
						return m.Registered
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *Headscale) getDirectPeers(m *Machine) (Machines, error) {
 | 
				
			||||||
 | 
						log.Trace().
 | 
				
			||||||
 | 
							Str("func", "getDirectPeers").
 | 
				
			||||||
 | 
							Str("machine", m.Name).
 | 
				
			||||||
 | 
							Msg("Finding direct peers")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						machines := Machines{}
 | 
				
			||||||
 | 
						if err := h.db.Where("namespace_id = ? AND machine_key <> ? AND registered",
 | 
				
			||||||
 | 
							m.NamespaceID, m.MachineKey).Find(&machines).Error; err != nil {
 | 
				
			||||||
 | 
							log.Error().Err(err).Msg("Error accessing db")
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sort.Slice(machines, func(i, j int) bool { return machines[i].ID < machines[j].ID })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Trace().
 | 
				
			||||||
 | 
							Str("func", "getDirectmachines").
 | 
				
			||||||
 | 
							Str("machine", m.Name).
 | 
				
			||||||
 | 
							Msgf("Found direct machines: %s", machines.String())
 | 
				
			||||||
 | 
						return machines, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *Headscale) getShared(m *Machine) (Machines, error) {
 | 
				
			||||||
 | 
						log.Trace().
 | 
				
			||||||
 | 
							Str("func", "getShared").
 | 
				
			||||||
 | 
							Str("machine", m.Name).
 | 
				
			||||||
 | 
							Msg("Finding shared peers")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// We fetch here machines that are shared to the `Namespace` of the machine we are getting peers for
 | 
				
			||||||
 | 
						sharedMachines := []SharedMachine{}
 | 
				
			||||||
 | 
						if err := h.db.Preload("Namespace").Preload("Machine").Where("namespace_id = ?",
 | 
				
			||||||
 | 
							m.NamespaceID).Find(&sharedMachines).Error; err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						peers := make(Machines, 0)
 | 
				
			||||||
 | 
						for _, sharedMachine := range sharedMachines {
 | 
				
			||||||
 | 
							peers = append(peers, sharedMachine.Machine)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sort.Slice(peers, func(i, j int) bool { return peers[i].ID < peers[j].ID })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Trace().
 | 
				
			||||||
 | 
							Str("func", "getShared").
 | 
				
			||||||
 | 
							Str("machine", m.Name).
 | 
				
			||||||
 | 
							Msgf("Found shared peers: %s", peers.String())
 | 
				
			||||||
 | 
						return peers, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *Headscale) getPeers(m *Machine) (Machines, error) {
 | 
				
			||||||
 | 
						direct, err := h.getDirectPeers(m)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Error().
 | 
				
			||||||
 | 
								Str("func", "getPeers").
 | 
				
			||||||
 | 
								Err(err).
 | 
				
			||||||
 | 
								Msg("Cannot fetch peers")
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						shared, err := h.getShared(m)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Error().
 | 
				
			||||||
 | 
								Str("func", "getDirectPeers").
 | 
				
			||||||
 | 
								Err(err).
 | 
				
			||||||
 | 
								Msg("Cannot fetch peers")
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						peers := append(direct, shared...)
 | 
				
			||||||
 | 
						sort.Slice(peers, func(i, j int) bool { return peers[i].ID < peers[j].ID })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Trace().
 | 
				
			||||||
 | 
							Str("func", "getShared").
 | 
				
			||||||
 | 
							Str("machine", m.Name).
 | 
				
			||||||
 | 
							Msgf("Found total peers: %s", peers.String())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return peers, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetMachine finds a Machine by name and namespace and returns the Machine struct
 | 
				
			||||||
 | 
					func (h *Headscale) GetMachine(namespace string, name string) (*Machine, error) {
 | 
				
			||||||
 | 
						machines, err := h.ListMachinesInNamespace(namespace)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, m := range *machines {
 | 
				
			||||||
 | 
							if m.Name == name {
 | 
				
			||||||
 | 
								return &m, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil, fmt.Errorf("machine not found")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetMachineByID finds a Machine by ID and returns the Machine struct
 | 
				
			||||||
 | 
					func (h *Headscale) GetMachineByID(id uint64) (*Machine, error) {
 | 
				
			||||||
 | 
						m := Machine{}
 | 
				
			||||||
 | 
						if result := h.db.Preload("Namespace").Find(&Machine{ID: id}).First(&m); result.Error != nil {
 | 
				
			||||||
 | 
							return nil, result.Error
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return &m, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetMachineByMachineKey finds a Machine by ID and returns the Machine struct
 | 
				
			||||||
 | 
					func (h *Headscale) GetMachineByMachineKey(mKey string) (*Machine, error) {
 | 
				
			||||||
 | 
						m := Machine{}
 | 
				
			||||||
 | 
						if result := h.db.Preload("Namespace").First(&m, "machine_key = ?", mKey); result.Error != nil {
 | 
				
			||||||
 | 
							return nil, result.Error
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return &m, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UpdateMachine takes a Machine struct pointer (typically already loaded from database
 | 
				
			||||||
 | 
					// and updates it with the latest data from the database.
 | 
				
			||||||
 | 
					func (h *Headscale) UpdateMachine(m *Machine) error {
 | 
				
			||||||
 | 
						if result := h.db.Find(m).First(&m); result.Error != nil {
 | 
				
			||||||
 | 
							return result.Error
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DeleteMachine softs deletes a Machine from the database
 | 
				
			||||||
 | 
					func (h *Headscale) DeleteMachine(m *Machine) error {
 | 
				
			||||||
 | 
						m.Registered = false
 | 
				
			||||||
 | 
						namespaceID := m.NamespaceID
 | 
				
			||||||
 | 
						h.db.Save(&m) // we mark it as unregistered, just in case
 | 
				
			||||||
 | 
						if err := h.db.Delete(&m).Error; err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return h.RequestMapUpdates(namespaceID)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// HardDeleteMachine hard deletes a Machine from the database
 | 
				
			||||||
 | 
					func (h *Headscale) HardDeleteMachine(m *Machine) error {
 | 
				
			||||||
 | 
						namespaceID := m.NamespaceID
 | 
				
			||||||
 | 
						if err := h.db.Unscoped().Delete(&m).Error; err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return h.RequestMapUpdates(namespaceID)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetHostInfo returns a Hostinfo struct for the machine
 | 
				
			||||||
 | 
					func (m *Machine) GetHostInfo() (*tailcfg.Hostinfo, error) {
 | 
				
			||||||
 | 
						hostinfo := tailcfg.Hostinfo{}
 | 
				
			||||||
 | 
						if len(m.HostInfo) != 0 {
 | 
				
			||||||
 | 
							hi, err := m.HostInfo.MarshalJSON()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							err = json.Unmarshal(hi, &hostinfo)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return &hostinfo, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *Headscale) notifyChangesToPeers(m *Machine) {
 | 
				
			||||||
 | 
						peers, err := h.getPeers(m)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Error().
 | 
				
			||||||
 | 
								Str("func", "notifyChangesToPeers").
 | 
				
			||||||
 | 
								Str("machine", m.Name).
 | 
				
			||||||
 | 
								Msgf("Error getting peers: %s", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, peer := range peers {
 | 
				
			||||||
 | 
							log.Info().
 | 
				
			||||||
 | 
								Str("func", "notifyChangesToPeers").
 | 
				
			||||||
 | 
								Str("machine", m.Name).
 | 
				
			||||||
 | 
								Str("peer", peer.Name).
 | 
				
			||||||
 | 
								Str("address", peer.IPAddress).
 | 
				
			||||||
 | 
								Msgf("Notifying peer %s (%s)", peer.Name, peer.IPAddress)
 | 
				
			||||||
 | 
							err := h.sendRequestOnUpdateChannel(&peer)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Info().
 | 
				
			||||||
 | 
									Str("func", "notifyChangesToPeers").
 | 
				
			||||||
 | 
									Str("machine", m.Name).
 | 
				
			||||||
 | 
									Str("peer", peer.Name).
 | 
				
			||||||
 | 
									Msgf("Peer %s does not have an open update client, skipping.", peer.Name)
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							log.Trace().
 | 
				
			||||||
 | 
								Str("func", "notifyChangesToPeers").
 | 
				
			||||||
 | 
								Str("machine", m.Name).
 | 
				
			||||||
 | 
								Str("peer", peer.Name).
 | 
				
			||||||
 | 
								Str("address", peer.IPAddress).
 | 
				
			||||||
 | 
								Msgf("Notified peer %s (%s)", peer.Name, peer.IPAddress)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *Headscale) getOrOpenUpdateChannel(m *Machine) <-chan struct{} {
 | 
				
			||||||
 | 
						var updateChan chan struct{}
 | 
				
			||||||
 | 
						if storedChan, ok := h.clientsUpdateChannels.Load(m.ID); ok {
 | 
				
			||||||
 | 
							if unwrapped, ok := storedChan.(chan struct{}); ok {
 | 
				
			||||||
 | 
								updateChan = unwrapped
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								log.Error().
 | 
				
			||||||
 | 
									Str("handler", "openUpdateChannel").
 | 
				
			||||||
 | 
									Str("machine", m.Name).
 | 
				
			||||||
 | 
									Msg("Failed to convert update channel to struct{}")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							log.Debug().
 | 
				
			||||||
 | 
								Str("handler", "openUpdateChannel").
 | 
				
			||||||
 | 
								Str("machine", m.Name).
 | 
				
			||||||
 | 
								Msg("Update channel not found, creating")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							updateChan = make(chan struct{})
 | 
				
			||||||
 | 
							h.clientsUpdateChannels.Store(m.ID, updateChan)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return updateChan
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *Headscale) closeUpdateChannel(m *Machine) {
 | 
				
			||||||
 | 
						h.clientsUpdateChannelMutex.Lock()
 | 
				
			||||||
 | 
						defer h.clientsUpdateChannelMutex.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if storedChan, ok := h.clientsUpdateChannels.Load(m.ID); ok {
 | 
				
			||||||
 | 
							if unwrapped, ok := storedChan.(chan struct{}); ok {
 | 
				
			||||||
 | 
								close(unwrapped)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						h.clientsUpdateChannels.Delete(m.ID)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *Headscale) sendRequestOnUpdateChannel(m *Machine) error {
 | 
				
			||||||
 | 
						h.clientsUpdateChannelMutex.Lock()
 | 
				
			||||||
 | 
						defer h.clientsUpdateChannelMutex.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pUp, ok := h.clientsUpdateChannels.Load(uint64(m.ID))
 | 
				
			||||||
 | 
						if ok {
 | 
				
			||||||
 | 
							log.Info().
 | 
				
			||||||
 | 
								Str("func", "requestUpdate").
 | 
				
			||||||
 | 
								Str("machine", m.Name).
 | 
				
			||||||
 | 
								Msgf("Notifying peer %s", m.Name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if update, ok := pUp.(chan struct{}); ok {
 | 
				
			||||||
 | 
								log.Trace().
 | 
				
			||||||
 | 
									Str("func", "requestUpdate").
 | 
				
			||||||
 | 
									Str("machine", m.Name).
 | 
				
			||||||
 | 
									Msgf("Update channel is %#v", update)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								updateRequestsToNode.Inc()
 | 
				
			||||||
 | 
								update <- struct{}{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								log.Trace().
 | 
				
			||||||
 | 
									Str("func", "requestUpdate").
 | 
				
			||||||
 | 
									Str("machine", m.Name).
 | 
				
			||||||
 | 
									Msgf("Notified machine %s", m.Name)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							err := errors.New("machine does not have an open update channel")
 | 
				
			||||||
 | 
							log.Info().
 | 
				
			||||||
 | 
								Str("func", "requestUpdate").
 | 
				
			||||||
 | 
								Str("machine", m.Name).
 | 
				
			||||||
 | 
								Msgf("Machine %s does not have an open update channel", m.Name)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *Headscale) isOutdated(m *Machine) bool {
 | 
				
			||||||
 | 
						err := h.UpdateMachine(m)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						lastChange := h.getLastStateChange(m.Namespace.Name)
 | 
				
			||||||
 | 
						log.Trace().
 | 
				
			||||||
 | 
							Str("func", "keepAlive").
 | 
				
			||||||
 | 
							Str("machine", m.Name).
 | 
				
			||||||
 | 
							Time("last_successful_update", *m.LastSuccessfulUpdate).
 | 
				
			||||||
 | 
							Time("last_state_change", lastChange).
 | 
				
			||||||
 | 
							Msgf("Checking if %s is missing updates", m.Name)
 | 
				
			||||||
 | 
						return m.LastSuccessfulUpdate.Before(lastChange)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m Machine) String() string {
 | 
				
			||||||
 | 
						return m.Name
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (ms Machines) String() string {
 | 
				
			||||||
 | 
						temp := make([]string, len(ms))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for index, machine := range ms {
 | 
				
			||||||
 | 
							temp[index] = machine.Name
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return fmt.Sprintf("[ %s ](%d)", strings.Join(temp, ", "), len(temp))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO(kradalby): Remove when we have generics...
 | 
				
			||||||
 | 
					func (ms MachinesP) String() string {
 | 
				
			||||||
 | 
						temp := make([]string, len(ms))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for index, machine := range ms {
 | 
				
			||||||
 | 
							temp[index] = machine.Name
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return fmt.Sprintf("[ %s ](%d)", strings.Join(temp, ", "), len(temp))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (ms Machines) toNodes(includeRoutes bool) ([]*tailcfg.Node, error) {
 | 
				
			||||||
 | 
						nodes := make([]*tailcfg.Node, len(ms))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for index, machine := range ms {
 | 
				
			||||||
 | 
							node, err := machine.toNode(includeRoutes)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							nodes[index] = node
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nodes, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// toNode converts a Machine into a Tailscale Node. includeRoutes is false for shared nodes
 | 
					// toNode converts a Machine into a Tailscale Node. includeRoutes is false for shared nodes
 | 
				
			||||||
// as per the expected behaviour in the official SaaS
 | 
					// as per the expected behaviour in the official SaaS
 | 
				
			||||||
func (m *Machine) toNode(baseDomain string, dnsConfig *tailcfg.DNSConfig, includeRoutes bool) (*tailcfg.Node, error) {
 | 
					func (m Machine) toNode(includeRoutes bool) (*tailcfg.Node, error) {
 | 
				
			||||||
	nKey, err := wgkey.ParseHex(m.NodeKey)
 | 
						nKey, err := wgkey.ParseHex(m.NodeKey)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
@ -147,17 +472,10 @@ func (m *Machine) toNode(baseDomain string, dnsConfig *tailcfg.DNSConfig, includ
 | 
				
			|||||||
		keyExpiry = time.Time{}
 | 
							keyExpiry = time.Time{}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var hostname string
 | 
					 | 
				
			||||||
	if dnsConfig != nil && dnsConfig.Proxied { // MagicDNS
 | 
					 | 
				
			||||||
		hostname = fmt.Sprintf("%s.%s.%s", m.Name, m.Namespace.Name, baseDomain)
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		hostname = m.Name
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	n := tailcfg.Node{
 | 
						n := tailcfg.Node{
 | 
				
			||||||
		ID:         tailcfg.NodeID(m.ID),                               // this is the actual ID
 | 
							ID:         tailcfg.NodeID(m.ID),                               // this is the actual ID
 | 
				
			||||||
		StableID:   tailcfg.StableNodeID(strconv.FormatUint(m.ID, 10)), // in headscale, unlike tailcontrol server, IDs are permanent
 | 
							StableID:   tailcfg.StableNodeID(strconv.FormatUint(m.ID, 10)), // in headscale, unlike tailcontrol server, IDs are permanent
 | 
				
			||||||
		Name:       hostname,
 | 
							Name:       hostinfo.Hostname,
 | 
				
			||||||
		User:       tailcfg.UserID(m.NamespaceID),
 | 
							User:       tailcfg.UserID(m.NamespaceID),
 | 
				
			||||||
		Key:        tailcfg.NodeKey(nKey),
 | 
							Key:        tailcfg.NodeKey(nKey),
 | 
				
			||||||
		KeyExpiry:  keyExpiry,
 | 
							KeyExpiry:  keyExpiry,
 | 
				
			||||||
@ -176,241 +494,5 @@ func (m *Machine) toNode(baseDomain string, dnsConfig *tailcfg.DNSConfig, includ
 | 
				
			|||||||
		MachineAuthorized: m.Registered,
 | 
							MachineAuthorized: m.Registered,
 | 
				
			||||||
		Capabilities:      []string{tailcfg.CapabilityFileSharing},
 | 
							Capabilities:      []string{tailcfg.CapabilityFileSharing},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	// TODO(juanfont): Node also has Sharer when is a shared node with info on the profile
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return &n, nil
 | 
						return &n, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
func (h *Headscale) getPeers(m Machine) (*[]*tailcfg.Node, error) {
 | 
					 | 
				
			||||||
	log.Trace().
 | 
					 | 
				
			||||||
		Str("func", "getPeers").
 | 
					 | 
				
			||||||
		Str("machine", m.Name).
 | 
					 | 
				
			||||||
		Msg("Finding peers")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	machines := []Machine{}
 | 
					 | 
				
			||||||
	if err := h.db.Preload("Namespace").Where("namespace_id = ? AND machine_key <> ? AND registered",
 | 
					 | 
				
			||||||
		m.NamespaceID, m.MachineKey).Find(&machines).Error; err != nil {
 | 
					 | 
				
			||||||
		log.Error().Err(err).Msg("Error accessing db")
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// We fetch here machines that are shared to the `Namespace` of the machine we are getting peers for
 | 
					 | 
				
			||||||
	sharedMachines := []SharedMachine{}
 | 
					 | 
				
			||||||
	if err := h.db.Preload("Namespace").Preload("Machine").Where("namespace_id = ?",
 | 
					 | 
				
			||||||
		m.NamespaceID).Find(&sharedMachines).Error; err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	peers := []*tailcfg.Node{}
 | 
					 | 
				
			||||||
	for _, mn := range machines {
 | 
					 | 
				
			||||||
		peer, err := mn.toNode(h.cfg.BaseDomain, h.cfg.DNSConfig, true)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return nil, err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		peers = append(peers, peer)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	for _, sharedMachine := range sharedMachines {
 | 
					 | 
				
			||||||
		peer, err := sharedMachine.Machine.toNode(h.cfg.BaseDomain, h.cfg.DNSConfig, false) // shared nodes do not expose their routes
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return nil, err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		peers = append(peers, peer)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	sort.Slice(peers, func(i, j int) bool { return peers[i].ID < peers[j].ID })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	log.Trace().
 | 
					 | 
				
			||||||
		Str("func", "getPeers").
 | 
					 | 
				
			||||||
		Str("machine", m.Name).
 | 
					 | 
				
			||||||
		Msgf("Found peers: %s", tailNodesToString(peers))
 | 
					 | 
				
			||||||
	return &peers, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// GetMachine finds a Machine by name and namespace and returns the Machine struct
 | 
					 | 
				
			||||||
func (h *Headscale) GetMachine(namespace string, name string) (*Machine, error) {
 | 
					 | 
				
			||||||
	machines, err := h.ListMachinesInNamespace(namespace)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for _, m := range *machines {
 | 
					 | 
				
			||||||
		if m.Name == name {
 | 
					 | 
				
			||||||
			return &m, nil
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return nil, fmt.Errorf("machine not found")
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// GetMachineByID finds a Machine by ID and returns the Machine struct
 | 
					 | 
				
			||||||
func (h *Headscale) GetMachineByID(id uint64) (*Machine, error) {
 | 
					 | 
				
			||||||
	m := Machine{}
 | 
					 | 
				
			||||||
	if result := h.db.Preload("Namespace").Find(&Machine{ID: id}).First(&m); result.Error != nil {
 | 
					 | 
				
			||||||
		return nil, result.Error
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return &m, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// UpdateMachine takes a Machine struct pointer (typically already loaded from database
 | 
					 | 
				
			||||||
// and updates it with the latest data from the database.
 | 
					 | 
				
			||||||
func (h *Headscale) UpdateMachine(m *Machine) error {
 | 
					 | 
				
			||||||
	if result := h.db.Find(m).First(&m); result.Error != nil {
 | 
					 | 
				
			||||||
		return result.Error
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// DeleteMachine softs deletes a Machine from the database
 | 
					 | 
				
			||||||
func (h *Headscale) DeleteMachine(m *Machine) error {
 | 
					 | 
				
			||||||
	m.Registered = false
 | 
					 | 
				
			||||||
	namespaceID := m.NamespaceID
 | 
					 | 
				
			||||||
	h.db.Save(&m) // we mark it as unregistered, just in case
 | 
					 | 
				
			||||||
	if err := h.db.Delete(&m).Error; err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return h.RequestMapUpdates(namespaceID)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// HardDeleteMachine hard deletes a Machine from the database
 | 
					 | 
				
			||||||
func (h *Headscale) HardDeleteMachine(m *Machine) error {
 | 
					 | 
				
			||||||
	namespaceID := m.NamespaceID
 | 
					 | 
				
			||||||
	if err := h.db.Unscoped().Delete(&m).Error; err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return h.RequestMapUpdates(namespaceID)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// GetHostInfo returns a Hostinfo struct for the machine
 | 
					 | 
				
			||||||
func (m *Machine) GetHostInfo() (*tailcfg.Hostinfo, error) {
 | 
					 | 
				
			||||||
	hostinfo := tailcfg.Hostinfo{}
 | 
					 | 
				
			||||||
	if len(m.HostInfo) != 0 {
 | 
					 | 
				
			||||||
		hi, err := m.HostInfo.MarshalJSON()
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return nil, err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		err = json.Unmarshal(hi, &hostinfo)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return nil, err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return &hostinfo, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (h *Headscale) notifyChangesToPeers(m *Machine) {
 | 
					 | 
				
			||||||
	peers, err := h.getPeers(*m)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Error().
 | 
					 | 
				
			||||||
			Str("func", "notifyChangesToPeers").
 | 
					 | 
				
			||||||
			Str("machine", m.Name).
 | 
					 | 
				
			||||||
			Msgf("Error getting peers: %s", err)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	for _, p := range *peers {
 | 
					 | 
				
			||||||
		log.Info().
 | 
					 | 
				
			||||||
			Str("func", "notifyChangesToPeers").
 | 
					 | 
				
			||||||
			Str("machine", m.Name).
 | 
					 | 
				
			||||||
			Str("peer", p.Name).
 | 
					 | 
				
			||||||
			Str("address", p.Addresses[0].String()).
 | 
					 | 
				
			||||||
			Msgf("Notifying peer %s (%s)", p.Name, p.Addresses[0])
 | 
					 | 
				
			||||||
		err := h.sendRequestOnUpdateChannel(p)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			log.Info().
 | 
					 | 
				
			||||||
				Str("func", "notifyChangesToPeers").
 | 
					 | 
				
			||||||
				Str("machine", m.Name).
 | 
					 | 
				
			||||||
				Str("peer", p.Name).
 | 
					 | 
				
			||||||
				Msgf("Peer %s does not have an open update client, skipping.", p.Name)
 | 
					 | 
				
			||||||
			continue
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		log.Trace().
 | 
					 | 
				
			||||||
			Str("func", "notifyChangesToPeers").
 | 
					 | 
				
			||||||
			Str("machine", m.Name).
 | 
					 | 
				
			||||||
			Str("peer", p.Name).
 | 
					 | 
				
			||||||
			Str("address", p.Addresses[0].String()).
 | 
					 | 
				
			||||||
			Msgf("Notified peer %s (%s)", p.Name, p.Addresses[0])
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (h *Headscale) getOrOpenUpdateChannel(m *Machine) <-chan struct{} {
 | 
					 | 
				
			||||||
	var updateChan chan struct{}
 | 
					 | 
				
			||||||
	if storedChan, ok := h.clientsUpdateChannels.Load(m.ID); ok {
 | 
					 | 
				
			||||||
		if unwrapped, ok := storedChan.(chan struct{}); ok {
 | 
					 | 
				
			||||||
			updateChan = unwrapped
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			log.Error().
 | 
					 | 
				
			||||||
				Str("handler", "openUpdateChannel").
 | 
					 | 
				
			||||||
				Str("machine", m.Name).
 | 
					 | 
				
			||||||
				Msg("Failed to convert update channel to struct{}")
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		log.Debug().
 | 
					 | 
				
			||||||
			Str("handler", "openUpdateChannel").
 | 
					 | 
				
			||||||
			Str("machine", m.Name).
 | 
					 | 
				
			||||||
			Msg("Update channel not found, creating")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		updateChan = make(chan struct{})
 | 
					 | 
				
			||||||
		h.clientsUpdateChannels.Store(m.ID, updateChan)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return updateChan
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (h *Headscale) closeUpdateChannel(m *Machine) {
 | 
					 | 
				
			||||||
	h.clientsUpdateChannelMutex.Lock()
 | 
					 | 
				
			||||||
	defer h.clientsUpdateChannelMutex.Unlock()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if storedChan, ok := h.clientsUpdateChannels.Load(m.ID); ok {
 | 
					 | 
				
			||||||
		if unwrapped, ok := storedChan.(chan struct{}); ok {
 | 
					 | 
				
			||||||
			close(unwrapped)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	h.clientsUpdateChannels.Delete(m.ID)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (h *Headscale) sendRequestOnUpdateChannel(m *tailcfg.Node) error {
 | 
					 | 
				
			||||||
	h.clientsUpdateChannelMutex.Lock()
 | 
					 | 
				
			||||||
	defer h.clientsUpdateChannelMutex.Unlock()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pUp, ok := h.clientsUpdateChannels.Load(uint64(m.ID))
 | 
					 | 
				
			||||||
	if ok {
 | 
					 | 
				
			||||||
		log.Info().
 | 
					 | 
				
			||||||
			Str("func", "requestUpdate").
 | 
					 | 
				
			||||||
			Str("machine", m.Name).
 | 
					 | 
				
			||||||
			Msgf("Notifying peer %s", m.Name)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if update, ok := pUp.(chan struct{}); ok {
 | 
					 | 
				
			||||||
			log.Trace().
 | 
					 | 
				
			||||||
				Str("func", "requestUpdate").
 | 
					 | 
				
			||||||
				Str("machine", m.Name).
 | 
					 | 
				
			||||||
				Msgf("Update channel is %#v", update)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			update <- struct{}{}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			log.Trace().
 | 
					 | 
				
			||||||
				Str("func", "requestUpdate").
 | 
					 | 
				
			||||||
				Str("machine", m.Name).
 | 
					 | 
				
			||||||
				Msgf("Notified machine %s", m.Name)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		err := errors.New("machine does not have an open update channel")
 | 
					 | 
				
			||||||
		log.Info().
 | 
					 | 
				
			||||||
			Str("func", "requestUpdate").
 | 
					 | 
				
			||||||
			Str("machine", m.Name).
 | 
					 | 
				
			||||||
			Msgf("Machine %s does not have an open update channel", m.Name)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (h *Headscale) isOutdated(m *Machine) bool {
 | 
					 | 
				
			||||||
	err := h.UpdateMachine(m)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return true
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	lastChange := h.getLastStateChange(m.Namespace.Name)
 | 
					 | 
				
			||||||
	log.Trace().
 | 
					 | 
				
			||||||
		Str("func", "keepAlive").
 | 
					 | 
				
			||||||
		Str("machine", m.Name).
 | 
					 | 
				
			||||||
		Time("last_successful_update", *m.LastSuccessfulUpdate).
 | 
					 | 
				
			||||||
		Time("last_state_change", lastChange).
 | 
					 | 
				
			||||||
		Msgf("Checking if %s is missing updates", m.Name)
 | 
					 | 
				
			||||||
	return m.LastSuccessfulUpdate.Before(lastChange)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,7 @@ package headscale
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"gopkg.in/check.v1"
 | 
						"gopkg.in/check.v1"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@ -16,7 +17,7 @@ func (s *Suite) TestGetMachine(c *check.C) {
 | 
				
			|||||||
	_, err = h.GetMachine("test", "testmachine")
 | 
						_, err = h.GetMachine("test", "testmachine")
 | 
				
			||||||
	c.Assert(err, check.NotNil)
 | 
						c.Assert(err, check.NotNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	m := Machine{
 | 
						m := &Machine{
 | 
				
			||||||
		ID:             0,
 | 
							ID:             0,
 | 
				
			||||||
		MachineKey:     "foo",
 | 
							MachineKey:     "foo",
 | 
				
			||||||
		NodeKey:        "bar",
 | 
							NodeKey:        "bar",
 | 
				
			||||||
@ -27,7 +28,7 @@ func (s *Suite) TestGetMachine(c *check.C) {
 | 
				
			|||||||
		RegisterMethod: "authKey",
 | 
							RegisterMethod: "authKey",
 | 
				
			||||||
		AuthKeyID:      uint(pak.ID),
 | 
							AuthKeyID:      uint(pak.ID),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	h.db.Save(&m)
 | 
						h.db.Save(m)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	m1, err := h.GetMachine("test", "testmachine")
 | 
						m1, err := h.GetMachine("test", "testmachine")
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
@ -116,3 +117,43 @@ func (s *Suite) TestHardDeleteMachine(c *check.C) {
 | 
				
			|||||||
	_, err = h.GetMachine(n.Name, "testmachine3")
 | 
						_, err = h.GetMachine(n.Name, "testmachine3")
 | 
				
			||||||
	c.Assert(err, check.NotNil)
 | 
						c.Assert(err, check.NotNil)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *Suite) TestGetDirectPeers(c *check.C) {
 | 
				
			||||||
 | 
						n, err := h.CreateNamespace("test")
 | 
				
			||||||
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pak, err := h.CreatePreAuthKey(n.Name, false, false, nil)
 | 
				
			||||||
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, err = h.GetMachineByID(0)
 | 
				
			||||||
 | 
						c.Assert(err, check.NotNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for i := 0; i <= 10; i++ {
 | 
				
			||||||
 | 
							m := Machine{
 | 
				
			||||||
 | 
								ID:             uint64(i),
 | 
				
			||||||
 | 
								MachineKey:     "foo" + strconv.Itoa(i),
 | 
				
			||||||
 | 
								NodeKey:        "bar" + strconv.Itoa(i),
 | 
				
			||||||
 | 
								DiscoKey:       "faa" + strconv.Itoa(i),
 | 
				
			||||||
 | 
								Name:           "testmachine" + strconv.Itoa(i),
 | 
				
			||||||
 | 
								NamespaceID:    n.ID,
 | 
				
			||||||
 | 
								Registered:     true,
 | 
				
			||||||
 | 
								RegisterMethod: "authKey",
 | 
				
			||||||
 | 
								AuthKeyID:      uint(pak.ID),
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							h.db.Save(&m)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						m1, err := h.GetMachineByID(0)
 | 
				
			||||||
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, err = m1.GetHostInfo()
 | 
				
			||||||
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						peers, err := h.getDirectPeers(m1)
 | 
				
			||||||
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						c.Assert(len(peers), check.Equals, 9)
 | 
				
			||||||
 | 
						c.Assert(peers[0].Name, check.Equals, "testmachine2")
 | 
				
			||||||
 | 
						c.Assert(peers[5].Name, check.Equals, "testmachine7")
 | 
				
			||||||
 | 
						c.Assert(peers[8].Name, check.Equals, "testmachine10")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										41
									
								
								metrics.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								metrics.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,41 @@
 | 
				
			|||||||
 | 
					package headscale
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"github.com/prometheus/client_golang/prometheus"
 | 
				
			||||||
 | 
						"github.com/prometheus/client_golang/prometheus/promauto"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const prometheusNamespace = "headscale"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						// This is a high cardinality metric (namespace x machines), we might want to make this
 | 
				
			||||||
 | 
						// configurable/opt-in in the future.
 | 
				
			||||||
 | 
						lastStateUpdate = promauto.NewGaugeVec(prometheus.GaugeOpts{
 | 
				
			||||||
 | 
							Namespace: prometheusNamespace,
 | 
				
			||||||
 | 
							Name:      "last_update_seconds",
 | 
				
			||||||
 | 
							Help:      "Time stamp in unix time when a machine or headscale was updated",
 | 
				
			||||||
 | 
						}, []string{"namespace", "machine"})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						machineRegistrations = promauto.NewCounterVec(prometheus.CounterOpts{
 | 
				
			||||||
 | 
							Namespace: prometheusNamespace,
 | 
				
			||||||
 | 
							Name:      "machine_registrations_total",
 | 
				
			||||||
 | 
							Help:      "The total amount of registered machine attempts",
 | 
				
			||||||
 | 
						}, []string{"action", "auth", "status", "namespace"})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						updateRequestsFromNode = promauto.NewCounterVec(prometheus.CounterOpts{
 | 
				
			||||||
 | 
							Namespace: prometheusNamespace,
 | 
				
			||||||
 | 
							Name:      "update_request_from_node_total",
 | 
				
			||||||
 | 
							Help:      "The number of updates requested by a node/update function",
 | 
				
			||||||
 | 
						}, []string{"state"})
 | 
				
			||||||
 | 
						updateRequestsToNode = promauto.NewCounter(prometheus.CounterOpts{
 | 
				
			||||||
 | 
							Namespace: prometheusNamespace,
 | 
				
			||||||
 | 
							Name:      "update_request_to_node_total",
 | 
				
			||||||
 | 
							Help:      "The number of calls/messages issued on a specific nodes update channel",
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						//TODO(kradalby): This is very debugging, we might want to remove it.
 | 
				
			||||||
 | 
						updateRequestsReceivedOnChannel = promauto.NewCounterVec(prometheus.CounterOpts{
 | 
				
			||||||
 | 
							Namespace: prometheusNamespace,
 | 
				
			||||||
 | 
							Name:      "update_request_received_on_channel_total",
 | 
				
			||||||
 | 
							Help:      "The number of update requests received on an update channel",
 | 
				
			||||||
 | 
						}, []string{"machine"})
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
@ -191,6 +191,7 @@ func (h *Headscale) checkForNamespacesPendingUpdates() {
 | 
				
			|||||||
			continue
 | 
								continue
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		for _, m := range *machines {
 | 
							for _, m := range *machines {
 | 
				
			||||||
 | 
								updateRequestsFromNode.WithLabelValues("namespace-update").Inc()
 | 
				
			||||||
			h.notifyChangesToPeers(&m)
 | 
								h.notifyChangesToPeers(&m)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										58
									
								
								poll.go
									
									
									
									
									
								
							
							
						
						
									
										58
									
								
								poll.go
									
									
									
									
									
								
							@ -51,14 +51,20 @@ func (h *Headscale) PollNetMapHandler(c *gin.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var m Machine
 | 
						m, err := h.GetMachineByMachineKey(mKey.HexString())
 | 
				
			||||||
	if result := h.db.Preload("Namespace").First(&m, "machine_key = ?", mKey.HexString()); errors.Is(result.Error, gorm.ErrRecordNotFound) {
 | 
						if err != nil {
 | 
				
			||||||
 | 
							if errors.Is(err, gorm.ErrRecordNotFound) {
 | 
				
			||||||
			log.Warn().
 | 
								log.Warn().
 | 
				
			||||||
				Str("handler", "PollNetMap").
 | 
									Str("handler", "PollNetMap").
 | 
				
			||||||
				Msgf("Ignoring request, cannot find machine with key %s", mKey.HexString())
 | 
									Msgf("Ignoring request, cannot find machine with key %s", mKey.HexString())
 | 
				
			||||||
			c.String(http.StatusUnauthorized, "")
 | 
								c.String(http.StatusUnauthorized, "")
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							log.Error().
 | 
				
			||||||
 | 
								Str("handler", "PollNetMap").
 | 
				
			||||||
 | 
								Msgf("Failed to fetch machine from the database with Machine key: %s", mKey.HexString())
 | 
				
			||||||
 | 
							c.String(http.StatusInternalServerError, "")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	log.Trace().
 | 
						log.Trace().
 | 
				
			||||||
		Str("handler", "PollNetMap").
 | 
							Str("handler", "PollNetMap").
 | 
				
			||||||
		Str("id", c.Param("id")).
 | 
							Str("id", c.Param("id")).
 | 
				
			||||||
@ -117,7 +123,7 @@ func (h *Headscale) PollNetMapHandler(c *gin.Context) {
 | 
				
			|||||||
			Str("handler", "PollNetMap").
 | 
								Str("handler", "PollNetMap").
 | 
				
			||||||
			Str("machine", m.Name).
 | 
								Str("machine", m.Name).
 | 
				
			||||||
			Msg("Client is starting up. Probably interested in a DERP map")
 | 
								Msg("Client is starting up. Probably interested in a DERP map")
 | 
				
			||||||
		c.Data(200, "application/json; charset=utf-8", *data)
 | 
							c.Data(200, "application/json; charset=utf-8", data)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -134,7 +140,7 @@ func (h *Headscale) PollNetMapHandler(c *gin.Context) {
 | 
				
			|||||||
		Str("id", c.Param("id")).
 | 
							Str("id", c.Param("id")).
 | 
				
			||||||
		Str("machine", m.Name).
 | 
							Str("machine", m.Name).
 | 
				
			||||||
		Msg("Loading or creating update channel")
 | 
							Msg("Loading or creating update channel")
 | 
				
			||||||
	updateChan := h.getOrOpenUpdateChannel(&m)
 | 
						updateChan := h.getOrOpenUpdateChannel(m)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pollDataChan := make(chan []byte)
 | 
						pollDataChan := make(chan []byte)
 | 
				
			||||||
	// defer close(pollData)
 | 
						// defer close(pollData)
 | 
				
			||||||
@ -149,11 +155,12 @@ func (h *Headscale) PollNetMapHandler(c *gin.Context) {
 | 
				
			|||||||
			Str("handler", "PollNetMap").
 | 
								Str("handler", "PollNetMap").
 | 
				
			||||||
			Str("machine", m.Name).
 | 
								Str("machine", m.Name).
 | 
				
			||||||
			Msg("Client sent endpoint update and is ok with a response without peer list")
 | 
								Msg("Client sent endpoint update and is ok with a response without peer list")
 | 
				
			||||||
		c.Data(200, "application/json; charset=utf-8", *data)
 | 
							c.Data(200, "application/json; charset=utf-8", data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// It sounds like we should update the nodes when we have received a endpoint update
 | 
							// It sounds like we should update the nodes when we have received a endpoint update
 | 
				
			||||||
		// even tho the comments in the tailscale code dont explicitly say so.
 | 
							// even tho the comments in the tailscale code dont explicitly say so.
 | 
				
			||||||
		go h.notifyChangesToPeers(&m)
 | 
							updateRequestsFromNode.WithLabelValues("endpoint-update").Inc()
 | 
				
			||||||
 | 
							go h.notifyChangesToPeers(m)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	} else if req.OmitPeers && req.Stream {
 | 
						} else if req.OmitPeers && req.Stream {
 | 
				
			||||||
		log.Warn().
 | 
							log.Warn().
 | 
				
			||||||
@ -172,13 +179,14 @@ func (h *Headscale) PollNetMapHandler(c *gin.Context) {
 | 
				
			|||||||
		Str("handler", "PollNetMap").
 | 
							Str("handler", "PollNetMap").
 | 
				
			||||||
		Str("machine", m.Name).
 | 
							Str("machine", m.Name).
 | 
				
			||||||
		Msg("Sending initial map")
 | 
							Msg("Sending initial map")
 | 
				
			||||||
	go func() { pollDataChan <- *data }()
 | 
						go func() { pollDataChan <- data }()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	log.Info().
 | 
						log.Info().
 | 
				
			||||||
		Str("handler", "PollNetMap").
 | 
							Str("handler", "PollNetMap").
 | 
				
			||||||
		Str("machine", m.Name).
 | 
							Str("machine", m.Name).
 | 
				
			||||||
		Msg("Notifying peers")
 | 
							Msg("Notifying peers")
 | 
				
			||||||
	go h.notifyChangesToPeers(&m)
 | 
						updateRequestsFromNode.WithLabelValues("full-update").Inc()
 | 
				
			||||||
 | 
						go h.notifyChangesToPeers(m)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	h.PollNetMapStream(c, m, req, mKey, pollDataChan, keepAliveChan, updateChan, cancelKeepAlive)
 | 
						h.PollNetMapStream(c, m, req, mKey, pollDataChan, keepAliveChan, updateChan, cancelKeepAlive)
 | 
				
			||||||
	log.Trace().
 | 
						log.Trace().
 | 
				
			||||||
@ -193,7 +201,7 @@ func (h *Headscale) PollNetMapHandler(c *gin.Context) {
 | 
				
			|||||||
// to the connected clients.
 | 
					// to the connected clients.
 | 
				
			||||||
func (h *Headscale) PollNetMapStream(
 | 
					func (h *Headscale) PollNetMapStream(
 | 
				
			||||||
	c *gin.Context,
 | 
						c *gin.Context,
 | 
				
			||||||
	m Machine,
 | 
						m *Machine,
 | 
				
			||||||
	req tailcfg.MapRequest,
 | 
						req tailcfg.MapRequest,
 | 
				
			||||||
	mKey wgkey.Key,
 | 
						mKey wgkey.Key,
 | 
				
			||||||
	pollDataChan chan []byte,
 | 
						pollDataChan chan []byte,
 | 
				
			||||||
@ -241,7 +249,7 @@ func (h *Headscale) PollNetMapStream(
 | 
				
			|||||||
				// TODO(kradalby): Abstract away all the database calls, this can cause race conditions
 | 
									// TODO(kradalby): Abstract away all the database calls, this can cause race conditions
 | 
				
			||||||
				// when an outdated machine object is kept alive, e.g. db is update from
 | 
									// when an outdated machine object is kept alive, e.g. db is update from
 | 
				
			||||||
				// command line, but then overwritten.
 | 
									// command line, but then overwritten.
 | 
				
			||||||
			err = h.UpdateMachine(&m)
 | 
								err = h.UpdateMachine(m)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				log.Error().
 | 
									log.Error().
 | 
				
			||||||
					Str("handler", "PollNetMapStream").
 | 
										Str("handler", "PollNetMapStream").
 | 
				
			||||||
@ -252,7 +260,10 @@ func (h *Headscale) PollNetMapStream(
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
			now := time.Now().UTC()
 | 
								now := time.Now().UTC()
 | 
				
			||||||
			m.LastSeen = &now
 | 
								m.LastSeen = &now
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								lastStateUpdate.WithLabelValues(m.Namespace.Name, m.Name).Set(float64(now.Unix()))
 | 
				
			||||||
			m.LastSuccessfulUpdate = &now
 | 
								m.LastSuccessfulUpdate = &now
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			h.db.Save(&m)
 | 
								h.db.Save(&m)
 | 
				
			||||||
			log.Trace().
 | 
								log.Trace().
 | 
				
			||||||
				Str("handler", "PollNetMapStream").
 | 
									Str("handler", "PollNetMapStream").
 | 
				
			||||||
@ -288,7 +299,7 @@ func (h *Headscale) PollNetMapStream(
 | 
				
			|||||||
				// TODO(kradalby): Abstract away all the database calls, this can cause race conditions
 | 
									// TODO(kradalby): Abstract away all the database calls, this can cause race conditions
 | 
				
			||||||
				// when an outdated machine object is kept alive, e.g. db is update from
 | 
									// when an outdated machine object is kept alive, e.g. db is update from
 | 
				
			||||||
				// command line, but then overwritten.
 | 
									// command line, but then overwritten.
 | 
				
			||||||
			err = h.UpdateMachine(&m)
 | 
								err = h.UpdateMachine(m)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				log.Error().
 | 
									log.Error().
 | 
				
			||||||
					Str("handler", "PollNetMapStream").
 | 
										Str("handler", "PollNetMapStream").
 | 
				
			||||||
@ -314,7 +325,8 @@ func (h *Headscale) PollNetMapStream(
 | 
				
			|||||||
				Str("machine", m.Name).
 | 
									Str("machine", m.Name).
 | 
				
			||||||
				Str("channel", "update").
 | 
									Str("channel", "update").
 | 
				
			||||||
				Msg("Received a request for update")
 | 
									Msg("Received a request for update")
 | 
				
			||||||
			if h.isOutdated(&m) {
 | 
								updateRequestsReceivedOnChannel.WithLabelValues(m.Name).Inc()
 | 
				
			||||||
 | 
								if h.isOutdated(m) {
 | 
				
			||||||
				log.Debug().
 | 
									log.Debug().
 | 
				
			||||||
					Str("handler", "PollNetMapStream").
 | 
										Str("handler", "PollNetMapStream").
 | 
				
			||||||
					Str("machine", m.Name).
 | 
										Str("machine", m.Name).
 | 
				
			||||||
@ -330,7 +342,7 @@ func (h *Headscale) PollNetMapStream(
 | 
				
			|||||||
						Err(err).
 | 
											Err(err).
 | 
				
			||||||
						Msg("Could not get the map update")
 | 
											Msg("Could not get the map update")
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				_, err = w.Write(*data)
 | 
									_, err = w.Write(data)
 | 
				
			||||||
				if err != nil {
 | 
									if err != nil {
 | 
				
			||||||
					log.Error().
 | 
										log.Error().
 | 
				
			||||||
						Str("handler", "PollNetMapStream").
 | 
											Str("handler", "PollNetMapStream").
 | 
				
			||||||
@ -353,7 +365,7 @@ func (h *Headscale) PollNetMapStream(
 | 
				
			|||||||
					// TODO(kradalby): Abstract away all the database calls, this can cause race conditions
 | 
										// TODO(kradalby): Abstract away all the database calls, this can cause race conditions
 | 
				
			||||||
					// when an outdated machine object is kept alive, e.g. db is update from
 | 
										// when an outdated machine object is kept alive, e.g. db is update from
 | 
				
			||||||
					// command line, but then overwritten.
 | 
										// command line, but then overwritten.
 | 
				
			||||||
				err = h.UpdateMachine(&m)
 | 
									err = h.UpdateMachine(m)
 | 
				
			||||||
				if err != nil {
 | 
									if err != nil {
 | 
				
			||||||
					log.Error().
 | 
										log.Error().
 | 
				
			||||||
						Str("handler", "PollNetMapStream").
 | 
											Str("handler", "PollNetMapStream").
 | 
				
			||||||
@ -363,7 +375,10 @@ func (h *Headscale) PollNetMapStream(
 | 
				
			|||||||
						Msg("Cannot update machine from database")
 | 
											Msg("Cannot update machine from database")
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				now := time.Now().UTC()
 | 
									now := time.Now().UTC()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									lastStateUpdate.WithLabelValues(m.Namespace.Name, m.Name).Set(float64(now.Unix()))
 | 
				
			||||||
				m.LastSuccessfulUpdate = &now
 | 
									m.LastSuccessfulUpdate = &now
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				h.db.Save(&m)
 | 
									h.db.Save(&m)
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				log.Trace().
 | 
									log.Trace().
 | 
				
			||||||
@ -383,7 +398,7 @@ func (h *Headscale) PollNetMapStream(
 | 
				
			|||||||
				// TODO: Abstract away all the database calls, this can cause race conditions
 | 
									// TODO: Abstract away all the database calls, this can cause race conditions
 | 
				
			||||||
				// when an outdated machine object is kept alive, e.g. db is update from
 | 
									// when an outdated machine object is kept alive, e.g. db is update from
 | 
				
			||||||
				// command line, but then overwritten.
 | 
									// command line, but then overwritten.
 | 
				
			||||||
			err := h.UpdateMachine(&m)
 | 
								err := h.UpdateMachine(m)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				log.Error().
 | 
									log.Error().
 | 
				
			||||||
					Str("handler", "PollNetMapStream").
 | 
										Str("handler", "PollNetMapStream").
 | 
				
			||||||
@ -408,21 +423,21 @@ func (h *Headscale) PollNetMapStream(
 | 
				
			|||||||
				Str("machine", m.Name).
 | 
									Str("machine", m.Name).
 | 
				
			||||||
				Str("channel", "Done").
 | 
									Str("channel", "Done").
 | 
				
			||||||
				Msg("Closing update channel")
 | 
									Msg("Closing update channel")
 | 
				
			||||||
			h.closeUpdateChannel(&m)
 | 
								h.closeUpdateChannel(m)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			close(pollDataChan)
 | 
					 | 
				
			||||||
			log.Trace().
 | 
								log.Trace().
 | 
				
			||||||
				Str("handler", "PollNetMapStream").
 | 
									Str("handler", "PollNetMapStream").
 | 
				
			||||||
				Str("machine", m.Name).
 | 
									Str("machine", m.Name).
 | 
				
			||||||
				Str("channel", "Done").
 | 
									Str("channel", "Done").
 | 
				
			||||||
				Msg("Closing pollData channel")
 | 
									Msg("Closing pollData channel")
 | 
				
			||||||
 | 
								close(pollDataChan)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			close(keepAliveChan)
 | 
					 | 
				
			||||||
			log.Trace().
 | 
								log.Trace().
 | 
				
			||||||
				Str("handler", "PollNetMapStream").
 | 
									Str("handler", "PollNetMapStream").
 | 
				
			||||||
				Str("machine", m.Name).
 | 
									Str("machine", m.Name).
 | 
				
			||||||
				Str("channel", "Done").
 | 
									Str("channel", "Done").
 | 
				
			||||||
				Msg("Closing keepAliveChan channel")
 | 
									Msg("Closing keepAliveChan channel")
 | 
				
			||||||
 | 
								close(keepAliveChan)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			return false
 | 
								return false
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@ -434,7 +449,7 @@ func (h *Headscale) scheduledPollWorker(
 | 
				
			|||||||
	keepAliveChan chan<- []byte,
 | 
						keepAliveChan chan<- []byte,
 | 
				
			||||||
	mKey wgkey.Key,
 | 
						mKey wgkey.Key,
 | 
				
			||||||
	req tailcfg.MapRequest,
 | 
						req tailcfg.MapRequest,
 | 
				
			||||||
	m Machine,
 | 
						m *Machine,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
	keepAliveTicker := time.NewTicker(60 * time.Second)
 | 
						keepAliveTicker := time.NewTicker(60 * time.Second)
 | 
				
			||||||
	updateCheckerTicker := time.NewTicker(30 * time.Second)
 | 
						updateCheckerTicker := time.NewTicker(30 * time.Second)
 | 
				
			||||||
@ -458,13 +473,12 @@ func (h *Headscale) scheduledPollWorker(
 | 
				
			|||||||
				Str("func", "keepAlive").
 | 
									Str("func", "keepAlive").
 | 
				
			||||||
				Str("machine", m.Name).
 | 
									Str("machine", m.Name).
 | 
				
			||||||
				Msg("Sending keepalive")
 | 
									Msg("Sending keepalive")
 | 
				
			||||||
			keepAliveChan <- *data
 | 
								keepAliveChan <- data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		case <-updateCheckerTicker.C:
 | 
							case <-updateCheckerTicker.C:
 | 
				
			||||||
			// Send an update request regardless of outdated or not, if data is sent
 | 
								// Send an update request regardless of outdated or not, if data is sent
 | 
				
			||||||
			// to the node is determined in the updateChan consumer block
 | 
								// to the node is determined in the updateChan consumer block
 | 
				
			||||||
			n, _ := h.toNode(m, true)
 | 
								err := h.sendRequestOnUpdateChannel(m)
 | 
				
			||||||
			err := h.sendRequestOnUpdateChannel(n)
 | 
					 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				log.Error().
 | 
									log.Error().
 | 
				
			||||||
					Str("func", "keepAlive").
 | 
										Str("func", "keepAlive").
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,6 @@ package headscale
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"gopkg.in/check.v1"
 | 
						"gopkg.in/check.v1"
 | 
				
			||||||
	"tailscale.com/tailcfg"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *Suite) TestBasicSharedNodesInNamespace(c *check.C) {
 | 
					func (s *Suite) TestBasicSharedNodesInNamespace(c *check.C) {
 | 
				
			||||||
@ -21,7 +20,7 @@ func (s *Suite) TestBasicSharedNodesInNamespace(c *check.C) {
 | 
				
			|||||||
	_, err = h.GetMachine(n1.Name, "test_get_shared_nodes_1")
 | 
						_, err = h.GetMachine(n1.Name, "test_get_shared_nodes_1")
 | 
				
			||||||
	c.Assert(err, check.NotNil)
 | 
						c.Assert(err, check.NotNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	m1 := Machine{
 | 
						m1 := &Machine{
 | 
				
			||||||
		ID:             0,
 | 
							ID:             0,
 | 
				
			||||||
		MachineKey:     "686824e749f3b7f2a5927ee6c1e422aee5292592d9179a271ed7b3e659b44a66",
 | 
							MachineKey:     "686824e749f3b7f2a5927ee6c1e422aee5292592d9179a271ed7b3e659b44a66",
 | 
				
			||||||
		NodeKey:        "686824e749f3b7f2a5927ee6c1e422aee5292592d9179a271ed7b3e659b44a66",
 | 
							NodeKey:        "686824e749f3b7f2a5927ee6c1e422aee5292592d9179a271ed7b3e659b44a66",
 | 
				
			||||||
@ -33,12 +32,12 @@ func (s *Suite) TestBasicSharedNodesInNamespace(c *check.C) {
 | 
				
			|||||||
		IPAddress:      "100.64.0.1",
 | 
							IPAddress:      "100.64.0.1",
 | 
				
			||||||
		AuthKeyID:      uint(pak1.ID),
 | 
							AuthKeyID:      uint(pak1.ID),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	h.db.Save(&m1)
 | 
						h.db.Save(m1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_, err = h.GetMachine(n1.Name, m1.Name)
 | 
						_, err = h.GetMachine(n1.Name, m1.Name)
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	m2 := Machine{
 | 
						m2 := &Machine{
 | 
				
			||||||
		ID:             1,
 | 
							ID:             1,
 | 
				
			||||||
		MachineKey:     "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
 | 
							MachineKey:     "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
 | 
				
			||||||
		NodeKey:        "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
 | 
							NodeKey:        "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
 | 
				
			||||||
@ -50,22 +49,22 @@ func (s *Suite) TestBasicSharedNodesInNamespace(c *check.C) {
 | 
				
			|||||||
		IPAddress:      "100.64.0.2",
 | 
							IPAddress:      "100.64.0.2",
 | 
				
			||||||
		AuthKeyID:      uint(pak2.ID),
 | 
							AuthKeyID:      uint(pak2.ID),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	h.db.Save(&m2)
 | 
						h.db.Save(m2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_, err = h.GetMachine(n2.Name, m2.Name)
 | 
						_, err = h.GetMachine(n2.Name, m2.Name)
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	p1s, err := h.getPeers(m1)
 | 
						p1s, err := h.getPeers(m1)
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
	c.Assert(len(*p1s), check.Equals, 0)
 | 
						c.Assert(len(p1s), check.Equals, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err = h.AddSharedMachineToNamespace(&m2, n1)
 | 
						err = h.AddSharedMachineToNamespace(m2, n1)
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	p1sAfter, err := h.getPeers(m1)
 | 
						p1sAfter, err := h.getPeers(m1)
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
	c.Assert(len(*p1sAfter), check.Equals, 1)
 | 
						c.Assert(len(p1sAfter), check.Equals, 1)
 | 
				
			||||||
	c.Assert((*p1sAfter)[0].ID, check.Equals, tailcfg.NodeID(m2.ID))
 | 
						c.Assert(p1sAfter[0].ID, check.Equals, m2.ID)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *Suite) TestSameNamespace(c *check.C) {
 | 
					func (s *Suite) TestSameNamespace(c *check.C) {
 | 
				
			||||||
@ -84,7 +83,7 @@ func (s *Suite) TestSameNamespace(c *check.C) {
 | 
				
			|||||||
	_, err = h.GetMachine(n1.Name, "test_get_shared_nodes_1")
 | 
						_, err = h.GetMachine(n1.Name, "test_get_shared_nodes_1")
 | 
				
			||||||
	c.Assert(err, check.NotNil)
 | 
						c.Assert(err, check.NotNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	m1 := Machine{
 | 
						m1 := &Machine{
 | 
				
			||||||
		ID:             0,
 | 
							ID:             0,
 | 
				
			||||||
		MachineKey:     "686824e749f3b7f2a5927ee6c1e422aee5292592d9179a271ed7b3e659b44a66",
 | 
							MachineKey:     "686824e749f3b7f2a5927ee6c1e422aee5292592d9179a271ed7b3e659b44a66",
 | 
				
			||||||
		NodeKey:        "686824e749f3b7f2a5927ee6c1e422aee5292592d9179a271ed7b3e659b44a66",
 | 
							NodeKey:        "686824e749f3b7f2a5927ee6c1e422aee5292592d9179a271ed7b3e659b44a66",
 | 
				
			||||||
@ -96,12 +95,12 @@ func (s *Suite) TestSameNamespace(c *check.C) {
 | 
				
			|||||||
		IPAddress:      "100.64.0.1",
 | 
							IPAddress:      "100.64.0.1",
 | 
				
			||||||
		AuthKeyID:      uint(pak1.ID),
 | 
							AuthKeyID:      uint(pak1.ID),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	h.db.Save(&m1)
 | 
						h.db.Save(m1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_, err = h.GetMachine(n1.Name, m1.Name)
 | 
						_, err = h.GetMachine(n1.Name, m1.Name)
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	m2 := Machine{
 | 
						m2 := &Machine{
 | 
				
			||||||
		ID:             1,
 | 
							ID:             1,
 | 
				
			||||||
		MachineKey:     "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
 | 
							MachineKey:     "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
 | 
				
			||||||
		NodeKey:        "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
 | 
							NodeKey:        "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
 | 
				
			||||||
@ -113,16 +112,16 @@ func (s *Suite) TestSameNamespace(c *check.C) {
 | 
				
			|||||||
		IPAddress:      "100.64.0.2",
 | 
							IPAddress:      "100.64.0.2",
 | 
				
			||||||
		AuthKeyID:      uint(pak2.ID),
 | 
							AuthKeyID:      uint(pak2.ID),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	h.db.Save(&m2)
 | 
						h.db.Save(m2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_, err = h.GetMachine(n2.Name, m2.Name)
 | 
						_, err = h.GetMachine(n2.Name, m2.Name)
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	p1s, err := h.getPeers(m1)
 | 
						p1s, err := h.getPeers(m1)
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
	c.Assert(len(*p1s), check.Equals, 0)
 | 
						c.Assert(len(p1s), check.Equals, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err = h.AddSharedMachineToNamespace(&m1, n1)
 | 
						err = h.AddSharedMachineToNamespace(m1, n1)
 | 
				
			||||||
	c.Assert(err, check.Equals, errorSameNamespace)
 | 
						c.Assert(err, check.Equals, errorSameNamespace)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -142,7 +141,7 @@ func (s *Suite) TestAlreadyShared(c *check.C) {
 | 
				
			|||||||
	_, err = h.GetMachine(n1.Name, "test_get_shared_nodes_1")
 | 
						_, err = h.GetMachine(n1.Name, "test_get_shared_nodes_1")
 | 
				
			||||||
	c.Assert(err, check.NotNil)
 | 
						c.Assert(err, check.NotNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	m1 := Machine{
 | 
						m1 := &Machine{
 | 
				
			||||||
		ID:             0,
 | 
							ID:             0,
 | 
				
			||||||
		MachineKey:     "686824e749f3b7f2a5927ee6c1e422aee5292592d9179a271ed7b3e659b44a66",
 | 
							MachineKey:     "686824e749f3b7f2a5927ee6c1e422aee5292592d9179a271ed7b3e659b44a66",
 | 
				
			||||||
		NodeKey:        "686824e749f3b7f2a5927ee6c1e422aee5292592d9179a271ed7b3e659b44a66",
 | 
							NodeKey:        "686824e749f3b7f2a5927ee6c1e422aee5292592d9179a271ed7b3e659b44a66",
 | 
				
			||||||
@ -154,12 +153,12 @@ func (s *Suite) TestAlreadyShared(c *check.C) {
 | 
				
			|||||||
		IPAddress:      "100.64.0.1",
 | 
							IPAddress:      "100.64.0.1",
 | 
				
			||||||
		AuthKeyID:      uint(pak1.ID),
 | 
							AuthKeyID:      uint(pak1.ID),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	h.db.Save(&m1)
 | 
						h.db.Save(m1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_, err = h.GetMachine(n1.Name, m1.Name)
 | 
						_, err = h.GetMachine(n1.Name, m1.Name)
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	m2 := Machine{
 | 
						m2 := &Machine{
 | 
				
			||||||
		ID:             1,
 | 
							ID:             1,
 | 
				
			||||||
		MachineKey:     "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
 | 
							MachineKey:     "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
 | 
				
			||||||
		NodeKey:        "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
 | 
							NodeKey:        "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
 | 
				
			||||||
@ -171,18 +170,18 @@ func (s *Suite) TestAlreadyShared(c *check.C) {
 | 
				
			|||||||
		IPAddress:      "100.64.0.2",
 | 
							IPAddress:      "100.64.0.2",
 | 
				
			||||||
		AuthKeyID:      uint(pak2.ID),
 | 
							AuthKeyID:      uint(pak2.ID),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	h.db.Save(&m2)
 | 
						h.db.Save(m2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_, err = h.GetMachine(n2.Name, m2.Name)
 | 
						_, err = h.GetMachine(n2.Name, m2.Name)
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	p1s, err := h.getPeers(m1)
 | 
						p1s, err := h.getPeers(m1)
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
	c.Assert(len(*p1s), check.Equals, 0)
 | 
						c.Assert(len(p1s), check.Equals, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err = h.AddSharedMachineToNamespace(&m2, n1)
 | 
						err = h.AddSharedMachineToNamespace(m2, n1)
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
	err = h.AddSharedMachineToNamespace(&m2, n1)
 | 
						err = h.AddSharedMachineToNamespace(m2, n1)
 | 
				
			||||||
	c.Assert(err, check.Equals, errorMachineAlreadyShared)
 | 
						c.Assert(err, check.Equals, errorMachineAlreadyShared)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -202,7 +201,7 @@ func (s *Suite) TestDoNotIncludeRoutesOnShared(c *check.C) {
 | 
				
			|||||||
	_, err = h.GetMachine(n1.Name, "test_get_shared_nodes_1")
 | 
						_, err = h.GetMachine(n1.Name, "test_get_shared_nodes_1")
 | 
				
			||||||
	c.Assert(err, check.NotNil)
 | 
						c.Assert(err, check.NotNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	m1 := Machine{
 | 
						m1 := &Machine{
 | 
				
			||||||
		ID:             0,
 | 
							ID:             0,
 | 
				
			||||||
		MachineKey:     "686824e749f3b7f2a5927ee6c1e422aee5292592d9179a271ed7b3e659b44a66",
 | 
							MachineKey:     "686824e749f3b7f2a5927ee6c1e422aee5292592d9179a271ed7b3e659b44a66",
 | 
				
			||||||
		NodeKey:        "686824e749f3b7f2a5927ee6c1e422aee5292592d9179a271ed7b3e659b44a66",
 | 
							NodeKey:        "686824e749f3b7f2a5927ee6c1e422aee5292592d9179a271ed7b3e659b44a66",
 | 
				
			||||||
@ -214,12 +213,12 @@ func (s *Suite) TestDoNotIncludeRoutesOnShared(c *check.C) {
 | 
				
			|||||||
		IPAddress:      "100.64.0.1",
 | 
							IPAddress:      "100.64.0.1",
 | 
				
			||||||
		AuthKeyID:      uint(pak1.ID),
 | 
							AuthKeyID:      uint(pak1.ID),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	h.db.Save(&m1)
 | 
						h.db.Save(m1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_, err = h.GetMachine(n1.Name, m1.Name)
 | 
						_, err = h.GetMachine(n1.Name, m1.Name)
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	m2 := Machine{
 | 
						m2 := &Machine{
 | 
				
			||||||
		ID:             1,
 | 
							ID:             1,
 | 
				
			||||||
		MachineKey:     "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
 | 
							MachineKey:     "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
 | 
				
			||||||
		NodeKey:        "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
 | 
							NodeKey:        "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
 | 
				
			||||||
@ -231,22 +230,22 @@ func (s *Suite) TestDoNotIncludeRoutesOnShared(c *check.C) {
 | 
				
			|||||||
		IPAddress:      "100.64.0.2",
 | 
							IPAddress:      "100.64.0.2",
 | 
				
			||||||
		AuthKeyID:      uint(pak2.ID),
 | 
							AuthKeyID:      uint(pak2.ID),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	h.db.Save(&m2)
 | 
						h.db.Save(m2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_, err = h.GetMachine(n2.Name, m2.Name)
 | 
						_, err = h.GetMachine(n2.Name, m2.Name)
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	p1s, err := h.getPeers(m1)
 | 
						p1s, err := h.getPeers(m1)
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
	c.Assert(len(*p1s), check.Equals, 0)
 | 
						c.Assert(len(p1s), check.Equals, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err = h.AddSharedMachineToNamespace(&m2, n1)
 | 
						err = h.AddSharedMachineToNamespace(m2, n1)
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	p1sAfter, err := h.getPeers(m1)
 | 
						p1sAfter, err := h.getPeers(m1)
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
	c.Assert(len(*p1sAfter), check.Equals, 1)
 | 
						c.Assert(len(p1sAfter), check.Equals, 1)
 | 
				
			||||||
	c.Assert(len((*p1sAfter)[0].AllowedIPs), check.Equals, 1)
 | 
						c.Assert(p1sAfter[0].Name, check.Equals, "test_get_shared_nodes_2")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *Suite) TestComplexSharingAcrossNamespaces(c *check.C) {
 | 
					func (s *Suite) TestComplexSharingAcrossNamespaces(c *check.C) {
 | 
				
			||||||
@ -274,7 +273,7 @@ func (s *Suite) TestComplexSharingAcrossNamespaces(c *check.C) {
 | 
				
			|||||||
	_, err = h.GetMachine(n1.Name, "test_get_shared_nodes_1")
 | 
						_, err = h.GetMachine(n1.Name, "test_get_shared_nodes_1")
 | 
				
			||||||
	c.Assert(err, check.NotNil)
 | 
						c.Assert(err, check.NotNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	m1 := Machine{
 | 
						m1 := &Machine{
 | 
				
			||||||
		ID:             0,
 | 
							ID:             0,
 | 
				
			||||||
		MachineKey:     "686824e749f3b7f2a5927ee6c1e422aee5292592d9179a271ed7b3e659b44a66",
 | 
							MachineKey:     "686824e749f3b7f2a5927ee6c1e422aee5292592d9179a271ed7b3e659b44a66",
 | 
				
			||||||
		NodeKey:        "686824e749f3b7f2a5927ee6c1e422aee5292592d9179a271ed7b3e659b44a66",
 | 
							NodeKey:        "686824e749f3b7f2a5927ee6c1e422aee5292592d9179a271ed7b3e659b44a66",
 | 
				
			||||||
@ -286,12 +285,12 @@ func (s *Suite) TestComplexSharingAcrossNamespaces(c *check.C) {
 | 
				
			|||||||
		IPAddress:      "100.64.0.1",
 | 
							IPAddress:      "100.64.0.1",
 | 
				
			||||||
		AuthKeyID:      uint(pak1.ID),
 | 
							AuthKeyID:      uint(pak1.ID),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	h.db.Save(&m1)
 | 
						h.db.Save(m1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_, err = h.GetMachine(n1.Name, m1.Name)
 | 
						_, err = h.GetMachine(n1.Name, m1.Name)
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	m2 := Machine{
 | 
						m2 := &Machine{
 | 
				
			||||||
		ID:             1,
 | 
							ID:             1,
 | 
				
			||||||
		MachineKey:     "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
 | 
							MachineKey:     "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
 | 
				
			||||||
		NodeKey:        "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
 | 
							NodeKey:        "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
 | 
				
			||||||
@ -303,12 +302,12 @@ func (s *Suite) TestComplexSharingAcrossNamespaces(c *check.C) {
 | 
				
			|||||||
		IPAddress:      "100.64.0.2",
 | 
							IPAddress:      "100.64.0.2",
 | 
				
			||||||
		AuthKeyID:      uint(pak2.ID),
 | 
							AuthKeyID:      uint(pak2.ID),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	h.db.Save(&m2)
 | 
						h.db.Save(m2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_, err = h.GetMachine(n2.Name, m2.Name)
 | 
						_, err = h.GetMachine(n2.Name, m2.Name)
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	m3 := Machine{
 | 
						m3 := &Machine{
 | 
				
			||||||
		ID:             2,
 | 
							ID:             2,
 | 
				
			||||||
		MachineKey:     "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
 | 
							MachineKey:     "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
 | 
				
			||||||
		NodeKey:        "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
 | 
							NodeKey:        "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
 | 
				
			||||||
@ -320,12 +319,12 @@ func (s *Suite) TestComplexSharingAcrossNamespaces(c *check.C) {
 | 
				
			|||||||
		IPAddress:      "100.64.0.3",
 | 
							IPAddress:      "100.64.0.3",
 | 
				
			||||||
		AuthKeyID:      uint(pak3.ID),
 | 
							AuthKeyID:      uint(pak3.ID),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	h.db.Save(&m3)
 | 
						h.db.Save(m3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_, err = h.GetMachine(n3.Name, m3.Name)
 | 
						_, err = h.GetMachine(n3.Name, m3.Name)
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	m4 := Machine{
 | 
						m4 := &Machine{
 | 
				
			||||||
		ID:             3,
 | 
							ID:             3,
 | 
				
			||||||
		MachineKey:     "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
 | 
							MachineKey:     "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
 | 
				
			||||||
		NodeKey:        "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
 | 
							NodeKey:        "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
 | 
				
			||||||
@ -337,23 +336,31 @@ func (s *Suite) TestComplexSharingAcrossNamespaces(c *check.C) {
 | 
				
			|||||||
		IPAddress:      "100.64.0.4",
 | 
							IPAddress:      "100.64.0.4",
 | 
				
			||||||
		AuthKeyID:      uint(pak4.ID),
 | 
							AuthKeyID:      uint(pak4.ID),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	h.db.Save(&m4)
 | 
						h.db.Save(m4)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_, err = h.GetMachine(n1.Name, m4.Name)
 | 
						_, err = h.GetMachine(n1.Name, m4.Name)
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	p1s, err := h.getPeers(m1)
 | 
						p1s, err := h.getPeers(m1)
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
	c.Assert(len(*p1s), check.Equals, 1) // nodes 1 and 4
 | 
						c.Assert(len(p1s), check.Equals, 1) // nodes 1 and 4
 | 
				
			||||||
 | 
						c.Assert(p1s[0].Name, check.Equals, "test_get_shared_nodes_4")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err = h.AddSharedMachineToNamespace(&m2, n1)
 | 
						err = h.AddSharedMachineToNamespace(m2, n1)
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	p1sAfter, err := h.getPeers(m1)
 | 
						p1sAfter, err := h.getPeers(m1)
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
	c.Assert(len(*p1sAfter), check.Equals, 2) // nodes 1, 2, 4
 | 
						c.Assert(len(p1sAfter), check.Equals, 2) // nodes 1, 2, 4
 | 
				
			||||||
 | 
						c.Assert(p1sAfter[0].Name, check.Equals, "test_get_shared_nodes_2")
 | 
				
			||||||
 | 
						c.Assert(p1sAfter[1].Name, check.Equals, "test_get_shared_nodes_4")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						node1shared, err := h.getShared(m1)
 | 
				
			||||||
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
						c.Assert(len(node1shared), check.Equals, 1) // nodes 1, 2, 4
 | 
				
			||||||
 | 
						c.Assert(node1shared[0].Name, check.Equals, "test_get_shared_nodes_2")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pAlone, err := h.getPeers(m3)
 | 
						pAlone, err := h.getPeers(m3)
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
	c.Assert(len(*pAlone), check.Equals, 0) // node 3 is alone
 | 
						c.Assert(len(pAlone), check.Equals, 0) // node 3 is alone
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user