Merge branch 'main' into magic-dns-support

This commit is contained in:
Juan Font 2021-10-04 19:45:12 +02:00 committed by GitHub
commit 040a18e6f8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 76 additions and 26 deletions

View file

@ -19,7 +19,7 @@ builds:
flags: flags:
- -mod=readonly - -mod=readonly
ldflags: ldflags:
- -s -w -X main.version={{.Version}} - -s -w -X github.com/juanfont/headscale/cmd/headscale/cli.Version=v{{.Version}}
- id: linux-armhf - id: linux-armhf
main: ./cmd/headscale/headscale.go main: ./cmd/headscale/headscale.go
mod_timestamp: '{{ .CommitTimestamp }}' mod_timestamp: '{{ .CommitTimestamp }}'
@ -39,7 +39,7 @@ builds:
flags: flags:
- -mod=readonly - -mod=readonly
ldflags: ldflags:
- -s -w -X main.version={{.Version}} - -s -w -X github.com/juanfont/headscale/cmd/headscale/cli.Version=v{{.Version}}
- id: linux-amd64 - id: linux-amd64
@ -54,6 +54,8 @@ builds:
- 7 - 7
main: ./cmd/headscale/headscale.go main: ./cmd/headscale/headscale.go
mod_timestamp: '{{ .CommitTimestamp }}' mod_timestamp: '{{ .CommitTimestamp }}'
ldflags:
- -s -w -X github.com/juanfont/headscale/cmd/headscale/cli.Version=v{{.Version}}
archives: archives:
- id: golang-cross - id: golang-cross

View file

@ -30,6 +30,17 @@ Headscale implements this coordination server.
- [x] Share nodes between ~~users~~ namespaces - [x] Share nodes between ~~users~~ namespaces
- [ ] MagicDNS / Smart DNS - [ ] MagicDNS / Smart DNS
## Client OS support
| OS | Supports headscale |
| --- | --- |
| Linux | Yes |
| OpenBSD | Yes |
| macOS | Yes (see `/apple` on your headscale for more information) |
| Windows | Yes |
| Android | [You need to compile the client yourself](https://github.com/juanfont/headscale/issues/58#issuecomment-885255270) |
| iOS | Not yet |
## Roadmap 🤷 ## Roadmap 🤷
Suggestions/PRs welcomed! Suggestions/PRs welcomed!
@ -114,7 +125,7 @@ Suggestions/PRs welcomed!
7. Add your first machine 7. Add your first machine
```shell ```shell
tailscale up -login-server YOUR_HEADSCALE_URL tailscale up --login-server YOUR_HEADSCALE_URL
``` ```
8. Navigate to the URL you will get with `tailscale up`, where you'll find your machine key. 8. Navigate to the URL you will get with `tailscale up`, where you'll find your machine key.
@ -154,7 +165,7 @@ Alternatively, you can use Auth Keys to register your machines:
2. Use the authkey from your machine to register it 2. Use the authkey from your machine to register it
```shell ```shell
tailscale up -login-server YOUR_HEADSCALE_URL --authkey YOURAUTHKEY tailscale up --login-server YOUR_HEADSCALE_URL --authkey YOURAUTHKEY
``` ```
If you create an authkey with the `--ephemeral` flag, that key will create ephemeral nodes. This implies that `--reusable` is true. If you create an authkey with the `--ephemeral` flag, that key will create ephemeral nodes. This implies that `--reusable` is true.

29
app.go
View file

@ -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"
"golang.org/x/crypto/acme"
"golang.org/x/crypto/acme/autocert" "golang.org/x/crypto/acme/autocert"
"gorm.io/gorm" "gorm.io/gorm"
"inet.af/netaddr" "inet.af/netaddr"
@ -46,6 +47,9 @@ type Config struct {
TLSCertPath string TLSCertPath string
TLSKeyPath string TLSKeyPath string
ACMEURL string
ACMEEmail string
DNSConfig *tailcfg.DNSConfig DNSConfig *tailcfg.DNSConfig
} }
@ -185,16 +189,18 @@ func (h *Headscale) Serve() error {
r.GET("/apple/:platform", h.ApplePlatformConfig) r.GET("/apple/:platform", h.ApplePlatformConfig)
var err error var err error
timeout := 30 * time.Second
go h.watchForKVUpdates(5000) go h.watchForKVUpdates(5000)
go h.expireEphemeralNodes(5000) go h.expireEphemeralNodes(5000)
s := &http.Server{ s := &http.Server{
Addr: h.cfg.Addr, Addr: h.cfg.Addr,
Handler: r, Handler: r,
ReadTimeout: timeout, ReadTimeout: 30 * time.Second,
WriteTimeout: timeout, // Go does not handle timeouts in HTTP very well, and there is
// no good way to handle streaming timeouts, therefore we need to
// keep this at unlimited and be careful to clean up connections
// https://blog.cloudflare.com/the-complete-guide-to-golang-net-http-timeouts/#aboutstreaming
WriteTimeout: 0,
} }
if h.cfg.TLSLetsEncryptHostname != "" { if h.cfg.TLSLetsEncryptHostname != "" {
@ -206,14 +212,14 @@ func (h *Headscale) Serve() error {
Prompt: autocert.AcceptTOS, Prompt: autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist(h.cfg.TLSLetsEncryptHostname), HostPolicy: autocert.HostWhitelist(h.cfg.TLSLetsEncryptHostname),
Cache: autocert.DirCache(h.cfg.TLSLetsEncryptCacheDir), Cache: autocert.DirCache(h.cfg.TLSLetsEncryptCacheDir),
Client: &acme.Client{
DirectoryURL: h.cfg.ACMEURL,
},
Email: h.cfg.ACMEEmail,
} }
s := &http.Server{
Addr: h.cfg.Addr, s.TLSConfig = m.TLSConfig()
TLSConfig: m.TLSConfig(),
Handler: r,
ReadTimeout: timeout,
WriteTimeout: timeout,
}
if h.cfg.TLSLetsEncryptChallengeType == "TLS-ALPN-01" { if h.cfg.TLSLetsEncryptChallengeType == "TLS-ALPN-01" {
// Configuration via autocert with TLS-ALPN-01 (https://tools.ietf.org/html/rfc8737) // Configuration via autocert with TLS-ALPN-01 (https://tools.ietf.org/html/rfc8737)
// The RFC requires that the validation is done on port 443; in other words, headscale // The RFC requires that the validation is done on port 443; in other words, headscale
@ -224,7 +230,6 @@ func (h *Headscale) Serve() error {
// port 80 for the certificate validation in addition to the headscale // port 80 for the certificate validation in addition to the headscale
// service, which can be configured to run on any other port. // service, which can be configured to run on any other port.
go func() { go func() {
log.Fatal(). log.Fatal().
Err(http.ListenAndServe(h.cfg.TLSLetsEncryptListen, m.HTTPHandler(http.HandlerFunc(h.redirect)))). Err(http.ListenAndServe(h.cfg.TLSLetsEncryptListen, m.HTTPHandler(http.HandlerFunc(h.redirect)))).
Msg("failed to set up a HTTP server") Msg("failed to set up a HTTP server")

View file

@ -186,6 +186,9 @@ func getHeadscaleApp() (*headscale.Headscale, error) {
TLSKeyPath: absPath(viper.GetString("tls_key_path")), TLSKeyPath: absPath(viper.GetString("tls_key_path")),
DNSConfig: dnsConfig, DNSConfig: dnsConfig,
ACMEEmail: viper.GetString("acme_email"),
ACMEURL: viper.GetString("acme_url"),
} }
h, err := headscale.NewHeadscale(cfg) h, err := headscale.NewHeadscale(cfg)

View file

@ -10,6 +10,8 @@
"db_name": "headscale", "db_name": "headscale",
"db_user": "foo", "db_user": "foo",
"db_pass": "bar", "db_pass": "bar",
"acme_url": "https://acme-v02.api.letsencrypt.org/directory",
"acme_email": "",
"tls_letsencrypt_hostname": "", "tls_letsencrypt_hostname": "",
"tls_letsencrypt_listen": ":http", "tls_letsencrypt_listen": ":http",
"tls_letsencrypt_cache_dir": ".cache", "tls_letsencrypt_cache_dir": ".cache",

View file

@ -6,6 +6,8 @@
"ephemeral_node_inactivity_timeout": "30m", "ephemeral_node_inactivity_timeout": "30m",
"db_type": "sqlite3", "db_type": "sqlite3",
"db_path": "db.sqlite", "db_path": "db.sqlite",
"acme_url": "https://acme-v02.api.letsencrypt.org/directory",
"acme_email": "",
"tls_letsencrypt_hostname": "", "tls_letsencrypt_hostname": "",
"tls_letsencrypt_listen": ":http", "tls_letsencrypt_listen": ":http",
"tls_letsencrypt_cache_dir": ".cache", "tls_letsencrypt_cache_dir": ".cache",

View file

@ -433,7 +433,7 @@ func (s *IntegrationTestSuite) TestPingAllPeers() {
command := []string{ command := []string{
"tailscale", "ping", "tailscale", "ping",
"--timeout=1s", "--timeout=1s",
"--c=20", "--c=10",
"--until-direct=true", "--until-direct=true",
ip.String(), ip.String(),
} }

View file

@ -317,7 +317,8 @@ func (h *Headscale) notifyChangesToPeers(m *Machine) {
Str("func", "notifyChangesToPeers"). Str("func", "notifyChangesToPeers").
Str("machine", m.Name). Str("machine", m.Name).
Str("peer", p.Name). Str("peer", p.Name).
Msgf("Peer %s does not appear to be polling", p.Name) Msgf("Peer %s does not have an open update client, skipping.", p.Name)
continue
} }
log.Trace(). log.Trace().
Str("func", "notifyChangesToPeers"). Str("func", "notifyChangesToPeers").
@ -388,11 +389,12 @@ func (h *Headscale) sendRequestOnUpdateChannel(m *tailcfg.Node) error {
Msgf("Notified machine %s", m.Name) Msgf("Notified machine %s", m.Name)
} }
} else { } else {
err := errors.New("machine does not have an open update channel")
log.Info(). log.Info().
Str("func", "requestUpdate"). Str("func", "requestUpdate").
Str("machine", m.Name). Str("machine", m.Name).
Msgf("Machine %s does not appear to be polling", m.Name) Msgf("Machine %s does not have an open update channel", m.Name)
return errors.New("machine does not seem to be polling") return err
} }
return nil return nil
} }

31
poll.go
View file

@ -230,6 +230,7 @@ func (h *Headscale) PollNetMapStream(
Str("channel", "pollData"). Str("channel", "pollData").
Err(err). Err(err).
Msg("Cannot write data") Msg("Cannot write data")
return false
} }
log.Trace(). log.Trace().
Str("handler", "PollNetMapStream"). Str("handler", "PollNetMapStream").
@ -237,7 +238,7 @@ func (h *Headscale) PollNetMapStream(
Str("channel", "pollData"). Str("channel", "pollData").
Int("bytes", len(data)). Int("bytes", len(data)).
Msg("Data from pollData channel written successfully") Msg("Data from pollData channel written successfully")
// TODO: 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)
@ -258,7 +259,7 @@ func (h *Headscale) PollNetMapStream(
Str("machine", m.Name). Str("machine", m.Name).
Str("channel", "pollData"). Str("channel", "pollData").
Int("bytes", len(data)). Int("bytes", len(data)).
Msg("Machine updated successfully after sending pollData") Msg("Machine entry in database updated successfully after sending pollData")
return true return true
case data := <-keepAliveChan: case data := <-keepAliveChan:
@ -276,6 +277,7 @@ func (h *Headscale) PollNetMapStream(
Str("channel", "keepAlive"). Str("channel", "keepAlive").
Err(err). Err(err).
Msg("Cannot write keep alive message") Msg("Cannot write keep alive message")
return false
} }
log.Trace(). log.Trace().
Str("handler", "PollNetMapStream"). Str("handler", "PollNetMapStream").
@ -283,7 +285,7 @@ func (h *Headscale) PollNetMapStream(
Str("channel", "keepAlive"). Str("channel", "keepAlive").
Int("bytes", len(data)). Int("bytes", len(data)).
Msg("Keep alive sent successfully") Msg("Keep alive sent successfully")
// TODO: 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)
@ -336,6 +338,7 @@ func (h *Headscale) PollNetMapStream(
Str("channel", "update"). Str("channel", "update").
Err(err). Err(err).
Msg("Could not write the map response") Msg("Could not write the map response")
return false
} }
log.Trace(). log.Trace().
Str("handler", "PollNetMapStream"). Str("handler", "PollNetMapStream").
@ -347,7 +350,7 @@ func (h *Headscale) PollNetMapStream(
// we sometimes end in a state were the update // we sometimes end in a state were the update
// is not picked up by a client and we use this // is not picked up by a client and we use this
// to determine if we should "force" an update. // to determine if we should "force" an update.
// TODO: 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)
@ -393,13 +396,33 @@ func (h *Headscale) PollNetMapStream(
m.LastSeen = &now m.LastSeen = &now
h.db.Save(&m) h.db.Save(&m)
log.Trace().
Str("handler", "PollNetMapStream").
Str("machine", m.Name).
Str("channel", "Done").
Msg("Cancelling keepAlive channel")
cancelKeepAlive <- struct{}{} cancelKeepAlive <- struct{}{}
log.Trace().
Str("handler", "PollNetMapStream").
Str("machine", m.Name).
Str("channel", "Done").
Msg("Closing update channel")
h.closeUpdateChannel(&m) h.closeUpdateChannel(&m)
close(pollDataChan) close(pollDataChan)
log.Trace().
Str("handler", "PollNetMapStream").
Str("machine", m.Name).
Str("channel", "Done").
Msg("Closing pollData channel")
close(keepAliveChan) close(keepAliveChan)
log.Trace().
Str("handler", "PollNetMapStream").
Str("machine", m.Name).
Str("channel", "Done").
Msg("Closing keepAliveChan channel")
return false return false
} }