tsnet.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. // Package tsnet provides Tailscale as a library.
  4. //
  5. // It is an experimental work in progress.
  6. package tsnet
  7. import (
  8. "context"
  9. crand "crypto/rand"
  10. "encoding/hex"
  11. "errors"
  12. "fmt"
  13. "io"
  14. "log"
  15. "math"
  16. "net"
  17. "net/http"
  18. "net/netip"
  19. "os"
  20. "path/filepath"
  21. "strings"
  22. "sync"
  23. "time"
  24. "tailscale.com/client/tailscale"
  25. "tailscale.com/control/controlclient"
  26. "tailscale.com/envknob"
  27. "tailscale.com/hostinfo"
  28. "tailscale.com/ipn"
  29. "tailscale.com/ipn/ipnlocal"
  30. "tailscale.com/ipn/ipnstate"
  31. "tailscale.com/ipn/localapi"
  32. "tailscale.com/ipn/store"
  33. "tailscale.com/ipn/store/mem"
  34. "tailscale.com/logpolicy"
  35. "tailscale.com/logtail"
  36. "tailscale.com/logtail/filch"
  37. "tailscale.com/net/memnet"
  38. "tailscale.com/net/tsdial"
  39. "tailscale.com/smallzstd"
  40. "tailscale.com/types/logger"
  41. "tailscale.com/util/mak"
  42. "tailscale.com/wgengine"
  43. "tailscale.com/wgengine/monitor"
  44. "tailscale.com/wgengine/netstack"
  45. )
  46. // Server is an embedded Tailscale server.
  47. //
  48. // Its exported fields may be changed until the first call to Listen.
  49. type Server struct {
  50. // Dir specifies the name of the directory to use for
  51. // state. If empty, a directory is selected automatically
  52. // under os.UserConfigDir (https://golang.org/pkg/os/#UserConfigDir).
  53. // based on the name of the binary.
  54. Dir string
  55. // Store specifies the state store to use.
  56. //
  57. // If nil, a new FileStore is initialized at `Dir/tailscaled.state`.
  58. // See tailscale.com/ipn/store for supported stores.
  59. //
  60. // Logs will automatically be uploaded to uploaded to log.tailscale.io,
  61. // where the configuration file for logging will be saved at
  62. // `Dir/tailscaled.log.conf`.
  63. Store ipn.StateStore
  64. // Hostname is the hostname to present to the control server.
  65. // If empty, the binary name is used.
  66. Hostname string
  67. // Logf, if non-nil, specifies the logger to use. By default,
  68. // log.Printf is used.
  69. Logf logger.Logf
  70. // Ephemeral, if true, specifies that the instance should register
  71. // as an Ephemeral node (https://tailscale.com/kb/1111/ephemeral-nodes/).
  72. Ephemeral bool
  73. // AuthKey, if non-empty, is the auth key to create the node
  74. // and will be preferred over the TS_AUTHKEY environment
  75. // variable. If the node is already created (from state
  76. // previously stored in in Store), then this field is not
  77. // used.
  78. AuthKey string
  79. // ControlURL optionally specifies the coordination server URL.
  80. // If empty, the Tailscale default is used.
  81. ControlURL string
  82. initOnce sync.Once
  83. initErr error
  84. lb *ipnlocal.LocalBackend
  85. netstack *netstack.Impl
  86. linkMon *monitor.Mon
  87. rootPath string // the state directory
  88. hostname string
  89. shutdownCtx context.Context
  90. shutdownCancel context.CancelFunc
  91. localAPICred string // basic auth password for localAPITCPListener
  92. localAPITCPListener net.Listener // optional loopback, restricted to PID
  93. localAPIListener net.Listener // in-memory, used by localClient
  94. localClient *tailscale.LocalClient // in-memory
  95. logbuffer *filch.Filch
  96. logtail *logtail.Logger
  97. logid string
  98. mu sync.Mutex
  99. listeners map[listenKey]*listener
  100. dialer *tsdial.Dialer
  101. }
  102. // Dial connects to the address on the tailnet.
  103. // It will start the server if it has not been started yet.
  104. func (s *Server) Dial(ctx context.Context, network, address string) (net.Conn, error) {
  105. if err := s.Start(); err != nil {
  106. return nil, err
  107. }
  108. return s.dialer.UserDial(ctx, network, address)
  109. }
  110. // HTTPClient returns an HTTP client that is configured to connect over Tailscale.
  111. //
  112. // This is useful if you need to have your tsnet services connect to other devices on
  113. // your tailnet.
  114. func (s *Server) HTTPClient() *http.Client {
  115. return &http.Client{
  116. Transport: &http.Transport{
  117. DialContext: s.Dial,
  118. },
  119. }
  120. }
  121. // LocalClient returns a LocalClient that speaks to s.
  122. //
  123. // It will start the server if it has not been started yet. If the server's
  124. // already been started successfully, it doesn't return an error.
  125. func (s *Server) LocalClient() (*tailscale.LocalClient, error) {
  126. if err := s.Start(); err != nil {
  127. return nil, err
  128. }
  129. return s.localClient, nil
  130. }
  131. // LoopbackLocalAPI returns a loopback ip:port listening for the "LocalAPI".
  132. //
  133. // As the LocalAPI is powerful, access to endpoints requires BOTH passing a
  134. // "Sec-Tailscale: localapi" HTTP header and passing cred as a basic auth.
  135. //
  136. // It will start the server and the local client listener if they have not
  137. // been started yet.
  138. //
  139. // If you only need to use the LocalAPI from Go, then prefer LocalClient
  140. // as it does not require communication via TCP.
  141. func (s *Server) LoopbackLocalAPI() (addr string, cred string, err error) {
  142. if err := s.Start(); err != nil {
  143. return "", "", err
  144. }
  145. if s.localAPITCPListener == nil {
  146. var cred [16]byte
  147. if _, err := crand.Read(cred[:]); err != nil {
  148. return "", "", err
  149. }
  150. s.localAPICred = hex.EncodeToString(cred[:])
  151. ln, err := net.Listen("tcp", "127.0.0.1:0")
  152. if err != nil {
  153. return "", "", err
  154. }
  155. s.localAPITCPListener = ln
  156. go func() {
  157. lah := localapi.NewHandler(s.lb, s.logf, s.logid)
  158. lah.PermitWrite = true
  159. lah.PermitRead = true
  160. lah.RequiredPassword = s.localAPICred
  161. h := &localSecHandler{h: lah, cred: s.localAPICred}
  162. if err := http.Serve(s.localAPITCPListener, h); err != nil {
  163. s.logf("localapi tcp serve error: %v", err)
  164. }
  165. }()
  166. }
  167. return s.localAPITCPListener.Addr().String(), s.localAPICred, nil
  168. }
  169. type localSecHandler struct {
  170. h http.Handler
  171. cred string
  172. }
  173. func (h *localSecHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  174. if r.Header.Get("Sec-Tailscale") != "localapi" {
  175. w.WriteHeader(403)
  176. io.WriteString(w, "missing 'Sec-Tailscale: localapi' header")
  177. return
  178. }
  179. h.h.ServeHTTP(w, r)
  180. }
  181. // Start connects the server to the tailnet.
  182. // Optional: any calls to Dial/Listen will also call Start.
  183. func (s *Server) Start() error {
  184. hostinfo.SetPackage("tsnet")
  185. s.initOnce.Do(s.doInit)
  186. return s.initErr
  187. }
  188. // Up connects the server to the tailnet and waits until it is running.
  189. // On success it returns the current status, including a Tailscale IP address.
  190. func (s *Server) Up(ctx context.Context) (*ipnstate.Status, error) {
  191. lc, err := s.LocalClient() // calls Start
  192. if err != nil {
  193. return nil, fmt.Errorf("tsnet.Up: %w", err)
  194. }
  195. watcher, err := lc.WatchIPNBus(ctx, ipn.NotifyInitialState|ipn.NotifyNoPrivateKeys)
  196. if err != nil {
  197. return nil, fmt.Errorf("tsnet.Up: %w", err)
  198. }
  199. defer watcher.Close()
  200. for {
  201. n, err := watcher.Next()
  202. if err != nil {
  203. return nil, fmt.Errorf("tsnet.Up: %w", err)
  204. }
  205. if n.ErrMessage != nil {
  206. return nil, fmt.Errorf("tsnet.Up: backend: %s", *n.ErrMessage)
  207. }
  208. if s := n.State; s != nil {
  209. if *s == ipn.Running {
  210. status, err := lc.Status(ctx)
  211. if err != nil {
  212. return nil, fmt.Errorf("tsnet.Up: %w", err)
  213. }
  214. if len(status.TailscaleIPs) == 0 {
  215. return nil, errors.New("tsnet.Up: running, but no ip")
  216. }
  217. return status, nil
  218. }
  219. // TODO: in the future, return an error on ipn.NeedsLogin
  220. // and ipn.NeedsMachineAuth to improve the UX of trying
  221. // out the tsnet package.
  222. //
  223. // Unfortunately today, even when using an AuthKey we
  224. // briefly see these states. It would be nice to fix.
  225. }
  226. }
  227. }
  228. // Close stops the server.
  229. //
  230. // It must not be called before or concurrently with Start.
  231. func (s *Server) Close() error {
  232. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  233. defer cancel()
  234. var wg sync.WaitGroup
  235. wg.Add(1)
  236. go func() {
  237. defer wg.Done()
  238. // Perform a best-effort final flush.
  239. if s.logtail != nil {
  240. s.logtail.Shutdown(ctx)
  241. }
  242. if s.logbuffer != nil {
  243. s.logbuffer.Close()
  244. }
  245. }()
  246. if _, isMemStore := s.Store.(*mem.Store); isMemStore && s.Ephemeral && s.lb != nil {
  247. wg.Add(1)
  248. go func() {
  249. defer wg.Done()
  250. // Perform a best-effort logout.
  251. s.lb.LogoutSync(ctx)
  252. }()
  253. }
  254. if s.netstack != nil {
  255. s.netstack.Close()
  256. s.netstack = nil
  257. }
  258. if s.shutdownCancel != nil {
  259. s.shutdownCancel()
  260. }
  261. if s.lb != nil {
  262. s.lb.Shutdown()
  263. }
  264. if s.linkMon != nil {
  265. s.linkMon.Close()
  266. }
  267. if s.dialer != nil {
  268. s.dialer.Close()
  269. }
  270. if s.localAPIListener != nil {
  271. s.localAPIListener.Close()
  272. }
  273. if s.localAPITCPListener != nil {
  274. s.localAPITCPListener.Close()
  275. }
  276. s.mu.Lock()
  277. defer s.mu.Unlock()
  278. for _, ln := range s.listeners {
  279. ln.Close()
  280. }
  281. s.listeners = nil
  282. wg.Wait()
  283. return nil
  284. }
  285. func (s *Server) doInit() {
  286. s.shutdownCtx, s.shutdownCancel = context.WithCancel(context.Background())
  287. if err := s.start(); err != nil {
  288. s.initErr = fmt.Errorf("tsnet: %w", err)
  289. }
  290. }
  291. func (s *Server) getAuthKey() string {
  292. if v := s.AuthKey; v != "" {
  293. return v
  294. }
  295. return os.Getenv("TS_AUTHKEY")
  296. }
  297. func (s *Server) start() (reterr error) {
  298. var closePool closeOnErrorPool
  299. defer closePool.closeAllIfError(&reterr)
  300. exe, err := os.Executable()
  301. if err != nil {
  302. return err
  303. }
  304. prog := strings.TrimSuffix(strings.ToLower(filepath.Base(exe)), ".exe")
  305. s.hostname = s.Hostname
  306. if s.hostname == "" {
  307. s.hostname = prog
  308. }
  309. s.rootPath = s.Dir
  310. if s.Store != nil {
  311. _, isMemStore := s.Store.(*mem.Store)
  312. if isMemStore && !s.Ephemeral {
  313. return fmt.Errorf("in-memory store is only supported for Ephemeral nodes")
  314. }
  315. }
  316. logf := s.logf
  317. if s.rootPath == "" {
  318. confDir, err := os.UserConfigDir()
  319. if err != nil {
  320. return err
  321. }
  322. s.rootPath, err = getTSNetDir(logf, confDir, prog)
  323. if err != nil {
  324. return err
  325. }
  326. if err := os.MkdirAll(s.rootPath, 0700); err != nil {
  327. return err
  328. }
  329. }
  330. if fi, err := os.Stat(s.rootPath); err != nil {
  331. return err
  332. } else if !fi.IsDir() {
  333. return fmt.Errorf("%v is not a directory", s.rootPath)
  334. }
  335. cfgPath := filepath.Join(s.rootPath, "tailscaled.log.conf")
  336. lpc, err := logpolicy.ConfigFromFile(cfgPath)
  337. switch {
  338. case os.IsNotExist(err):
  339. lpc = logpolicy.NewConfig(logtail.CollectionNode)
  340. if err := lpc.Save(cfgPath); err != nil {
  341. return fmt.Errorf("logpolicy.Config.Save for %v: %w", cfgPath, err)
  342. }
  343. case err != nil:
  344. return fmt.Errorf("logpolicy.LoadConfig for %v: %w", cfgPath, err)
  345. }
  346. if err := lpc.Validate(logtail.CollectionNode); err != nil {
  347. return fmt.Errorf("logpolicy.Config.Validate for %v: %w", cfgPath, err)
  348. }
  349. s.logid = lpc.PublicID.String()
  350. s.logbuffer, err = filch.New(filepath.Join(s.rootPath, "tailscaled"), filch.Options{ReplaceStderr: false})
  351. if err != nil {
  352. return fmt.Errorf("error creating filch: %w", err)
  353. }
  354. closePool.add(s.logbuffer)
  355. c := logtail.Config{
  356. Collection: lpc.Collection,
  357. PrivateID: lpc.PrivateID,
  358. Stderr: io.Discard, // log everything to Buffer
  359. Buffer: s.logbuffer,
  360. NewZstdEncoder: func() logtail.Encoder {
  361. w, err := smallzstd.NewEncoder(nil)
  362. if err != nil {
  363. panic(err)
  364. }
  365. return w
  366. },
  367. HTTPC: &http.Client{Transport: logpolicy.NewLogtailTransport(logtail.DefaultHost)},
  368. }
  369. s.logtail = logtail.NewLogger(c, logf)
  370. closePool.addFunc(func() { s.logtail.Shutdown(context.Background()) })
  371. s.linkMon, err = monitor.New(logf)
  372. if err != nil {
  373. return err
  374. }
  375. closePool.add(s.linkMon)
  376. s.dialer = &tsdial.Dialer{Logf: logf} // mutated below (before used)
  377. eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{
  378. ListenPort: 0,
  379. LinkMonitor: s.linkMon,
  380. Dialer: s.dialer,
  381. })
  382. if err != nil {
  383. return err
  384. }
  385. closePool.add(s.dialer)
  386. tunDev, magicConn, dns, ok := eng.(wgengine.InternalsGetter).GetInternals()
  387. if !ok {
  388. return fmt.Errorf("%T is not a wgengine.InternalsGetter", eng)
  389. }
  390. ns, err := netstack.Create(logf, tunDev, eng, magicConn, s.dialer, dns)
  391. if err != nil {
  392. return fmt.Errorf("netstack.Create: %w", err)
  393. }
  394. ns.ProcessLocalIPs = true
  395. ns.ForwardTCPIn = s.forwardTCP
  396. s.netstack = ns
  397. s.dialer.UseNetstackForIP = func(ip netip.Addr) bool {
  398. _, ok := eng.PeerForIP(ip)
  399. return ok
  400. }
  401. s.dialer.NetstackDialTCP = func(ctx context.Context, dst netip.AddrPort) (net.Conn, error) {
  402. return ns.DialContextTCP(ctx, dst)
  403. }
  404. if s.Store == nil {
  405. stateFile := filepath.Join(s.rootPath, "tailscaled.state")
  406. logf("tsnet running state path %s", stateFile)
  407. s.Store, err = store.New(logf, stateFile)
  408. if err != nil {
  409. return err
  410. }
  411. }
  412. loginFlags := controlclient.LoginDefault
  413. if s.Ephemeral {
  414. loginFlags = controlclient.LoginEphemeral
  415. }
  416. lb, err := ipnlocal.NewLocalBackend(logf, s.logid, s.Store, s.dialer, eng, loginFlags)
  417. if err != nil {
  418. return fmt.Errorf("NewLocalBackend: %v", err)
  419. }
  420. lb.SetVarRoot(s.rootPath)
  421. logf("tsnet starting with hostname %q, varRoot %q", s.hostname, s.rootPath)
  422. s.lb = lb
  423. if err := ns.Start(lb); err != nil {
  424. return fmt.Errorf("failed to start netstack: %w", err)
  425. }
  426. closePool.addFunc(func() { s.lb.Shutdown() })
  427. lb.SetDecompressor(func() (controlclient.Decompressor, error) {
  428. return smallzstd.NewDecoder(nil)
  429. })
  430. prefs := ipn.NewPrefs()
  431. prefs.Hostname = s.hostname
  432. prefs.WantRunning = true
  433. prefs.ControlURL = s.ControlURL
  434. authKey := s.getAuthKey()
  435. err = lb.Start(ipn.Options{
  436. UpdatePrefs: prefs,
  437. AuthKey: authKey,
  438. })
  439. if err != nil {
  440. return fmt.Errorf("starting backend: %w", err)
  441. }
  442. st := lb.State()
  443. if st == ipn.NeedsLogin || envknob.Bool("TSNET_FORCE_LOGIN") {
  444. logf("LocalBackend state is %v; running StartLoginInteractive...", st)
  445. s.lb.StartLoginInteractive()
  446. } else if authKey != "" {
  447. logf("Authkey is set; but state is %v. Ignoring authkey. Re-run with TSNET_FORCE_LOGIN=1 to force use of authkey.", st)
  448. }
  449. go s.printAuthURLLoop()
  450. // Run the localapi handler, to allow fetching LetsEncrypt certs.
  451. lah := localapi.NewHandler(lb, logf, s.logid)
  452. lah.PermitWrite = true
  453. lah.PermitRead = true
  454. // Create an in-process listener.
  455. // nettest.Listen provides a in-memory pipe based implementation for net.Conn.
  456. lal := memnet.Listen("local-tailscaled.sock:80")
  457. s.localAPIListener = lal
  458. s.localClient = &tailscale.LocalClient{Dial: lal.Dial}
  459. go func() {
  460. if err := http.Serve(lal, lah); err != nil {
  461. logf("localapi serve error: %v", err)
  462. }
  463. }()
  464. closePool.add(s.localAPIListener)
  465. return nil
  466. }
  467. type closeOnErrorPool []func()
  468. func (p *closeOnErrorPool) add(c io.Closer) { *p = append(*p, func() { c.Close() }) }
  469. func (p *closeOnErrorPool) addFunc(fn func()) { *p = append(*p, fn) }
  470. func (p closeOnErrorPool) closeAllIfError(errp *error) {
  471. if *errp != nil {
  472. for _, closeFn := range p {
  473. closeFn()
  474. }
  475. }
  476. }
  477. func (s *Server) logf(format string, a ...interface{}) {
  478. if s.logtail != nil {
  479. s.logtail.Logf(format, a...)
  480. }
  481. if s.Logf != nil {
  482. s.Logf(format, a...)
  483. return
  484. }
  485. log.Printf(format, a...)
  486. }
  487. // printAuthURLLoop loops once every few seconds while the server is still running and
  488. // is in NeedsLogin state, printing out the auth URL.
  489. func (s *Server) printAuthURLLoop() {
  490. for {
  491. if s.shutdownCtx.Err() != nil {
  492. return
  493. }
  494. if st := s.lb.State(); st != ipn.NeedsLogin {
  495. s.logf("printAuthURLLoop: state is %v; stopping", st)
  496. return
  497. }
  498. st := s.lb.StatusWithoutPeers()
  499. if st.AuthURL != "" {
  500. s.logf("To start this tsnet server, restart with TS_AUTHKEY set, or go to: %s", st.AuthURL)
  501. }
  502. select {
  503. case <-time.After(5 * time.Second):
  504. case <-s.shutdownCtx.Done():
  505. return
  506. }
  507. }
  508. }
  509. func (s *Server) forwardTCP(c net.Conn, port uint16) {
  510. s.mu.Lock()
  511. ln, ok := s.listeners[listenKey{"tcp", "", port}]
  512. s.mu.Unlock()
  513. if !ok {
  514. c.Close()
  515. return
  516. }
  517. t := time.NewTimer(time.Second)
  518. defer t.Stop()
  519. select {
  520. case ln.conn <- c:
  521. case <-t.C:
  522. c.Close()
  523. }
  524. }
  525. // getTSNetDir usually just returns filepath.Join(confDir, "tsnet-"+prog)
  526. // with no error.
  527. //
  528. // One special case is that it renames old "tslib-" directories to
  529. // "tsnet-", and that rename might return an error.
  530. //
  531. // TODO(bradfitz): remove this maybe 6 months after 2022-03-17,
  532. // once people (notably Tailscale corp services) have updated.
  533. func getTSNetDir(logf logger.Logf, confDir, prog string) (string, error) {
  534. oldPath := filepath.Join(confDir, "tslib-"+prog)
  535. newPath := filepath.Join(confDir, "tsnet-"+prog)
  536. fi, err := os.Lstat(oldPath)
  537. if os.IsNotExist(err) {
  538. // Common path.
  539. return newPath, nil
  540. }
  541. if err != nil {
  542. return "", err
  543. }
  544. if !fi.IsDir() {
  545. return "", fmt.Errorf("expected old tslib path %q to be a directory; got %v", oldPath, fi.Mode())
  546. }
  547. // At this point, oldPath exists and is a directory. But does
  548. // the new path exist?
  549. fi, err = os.Lstat(newPath)
  550. if err == nil && fi.IsDir() {
  551. // New path already exists somehow. Ignore the old one and
  552. // don't try to migrate it.
  553. return newPath, nil
  554. }
  555. if err != nil && !os.IsNotExist(err) {
  556. return "", err
  557. }
  558. if err := os.Rename(oldPath, newPath); err != nil {
  559. return "", err
  560. }
  561. logf("renamed old tsnet state storage directory %q to %q", oldPath, newPath)
  562. return newPath, nil
  563. }
  564. // APIClient returns a tailscale.Client that can be used to make authenticated
  565. // requests to the Tailscale control server.
  566. // It requires the user to set tailscale.I_Acknowledge_This_API_Is_Unstable.
  567. func (s *Server) APIClient() (*tailscale.Client, error) {
  568. if !tailscale.I_Acknowledge_This_API_Is_Unstable {
  569. return nil, errors.New("use of Client without setting I_Acknowledge_This_API_Is_Unstable")
  570. }
  571. if err := s.Start(); err != nil {
  572. return nil, err
  573. }
  574. c := tailscale.NewClient("-", nil)
  575. c.HTTPClient = &http.Client{Transport: s.lb.KeyProvingNoiseRoundTripper()}
  576. return c, nil
  577. }
  578. // Listen announces only on the Tailscale network.
  579. // It will start the server if it has not been started yet.
  580. func (s *Server) Listen(network, addr string) (net.Listener, error) {
  581. switch network {
  582. case "", "tcp", "tcp4", "tcp6":
  583. default:
  584. return nil, errors.New("unsupported network type")
  585. }
  586. host, portStr, err := net.SplitHostPort(addr)
  587. if err != nil {
  588. return nil, fmt.Errorf("tsnet: %w", err)
  589. }
  590. port, err := net.LookupPort(network, portStr)
  591. if err != nil || port < 0 || port > math.MaxUint16 {
  592. return nil, fmt.Errorf("invalid port: %w", err)
  593. }
  594. if err := s.Start(); err != nil {
  595. return nil, err
  596. }
  597. key := listenKey{network, host, uint16(port)}
  598. ln := &listener{
  599. s: s,
  600. key: key,
  601. addr: addr,
  602. conn: make(chan net.Conn),
  603. }
  604. s.mu.Lock()
  605. if _, ok := s.listeners[key]; ok {
  606. s.mu.Unlock()
  607. return nil, fmt.Errorf("tsnet: listener already open for %s, %s", network, addr)
  608. }
  609. mak.Set(&s.listeners, key, ln)
  610. s.mu.Unlock()
  611. return ln, nil
  612. }
  613. type listenKey struct {
  614. network string
  615. host string
  616. port uint16
  617. }
  618. type listener struct {
  619. s *Server
  620. key listenKey
  621. addr string
  622. conn chan net.Conn
  623. }
  624. func (ln *listener) Accept() (net.Conn, error) {
  625. c, ok := <-ln.conn
  626. if !ok {
  627. return nil, fmt.Errorf("tsnet: %w", net.ErrClosed)
  628. }
  629. return c, nil
  630. }
  631. func (ln *listener) Addr() net.Addr { return addr{ln} }
  632. func (ln *listener) Close() error {
  633. ln.s.mu.Lock()
  634. defer ln.s.mu.Unlock()
  635. if v, ok := ln.s.listeners[ln.key]; ok && v == ln {
  636. delete(ln.s.listeners, ln.key)
  637. close(ln.conn)
  638. }
  639. return nil
  640. }
  641. // Server returns the tsnet Server associated with the listener.
  642. func (ln *listener) Server() *Server { return ln.s }
  643. type addr struct{ ln *listener }
  644. func (a addr) Network() string { return a.ln.key.network }
  645. func (a addr) String() string { return a.ln.addr }