localapi_test.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package localapi
  4. import (
  5. "bytes"
  6. "encoding/json"
  7. "io"
  8. "net/http"
  9. "net/http/httptest"
  10. "net/netip"
  11. "net/url"
  12. "strings"
  13. "testing"
  14. "tailscale.com/client/tailscale/apitype"
  15. "tailscale.com/hostinfo"
  16. "tailscale.com/ipn/ipnlocal"
  17. "tailscale.com/tailcfg"
  18. "tailscale.com/tstest"
  19. )
  20. func TestValidHost(t *testing.T) {
  21. tests := []struct {
  22. host string
  23. valid bool
  24. }{
  25. {"", true},
  26. {apitype.LocalAPIHost, true},
  27. {"localhost:9109", false},
  28. {"127.0.0.1:9110", false},
  29. {"[::1]:9111", false},
  30. {"100.100.100.100:41112", false},
  31. {"10.0.0.1:41112", false},
  32. {"37.16.9.210:41112", false},
  33. }
  34. for _, test := range tests {
  35. t.Run(test.host, func(t *testing.T) {
  36. h := &Handler{}
  37. if got := h.validHost(test.host); got != test.valid {
  38. t.Errorf("validHost(%q)=%v, want %v", test.host, got, test.valid)
  39. }
  40. })
  41. }
  42. }
  43. func TestSetPushDeviceToken(t *testing.T) {
  44. tstest.Replace(t, &validLocalHostForTesting, true)
  45. h := &Handler{
  46. PermitWrite: true,
  47. b: &ipnlocal.LocalBackend{},
  48. }
  49. s := httptest.NewServer(h)
  50. defer s.Close()
  51. c := s.Client()
  52. want := "my-test-device-token"
  53. body, err := json.Marshal(apitype.SetPushDeviceTokenRequest{PushDeviceToken: want})
  54. if err != nil {
  55. t.Fatal(err)
  56. }
  57. req, err := http.NewRequest("POST", s.URL+"/localapi/v0/set-push-device-token", bytes.NewReader(body))
  58. if err != nil {
  59. t.Fatal(err)
  60. }
  61. res, err := c.Do(req)
  62. if err != nil {
  63. t.Fatal(err)
  64. }
  65. body, err = io.ReadAll(res.Body)
  66. if err != nil {
  67. t.Fatal(err)
  68. }
  69. if res.StatusCode != 200 {
  70. t.Errorf("res.StatusCode=%d, want 200. body: %s", res.StatusCode, body)
  71. }
  72. if got := hostinfo.New().PushDeviceToken; got != want {
  73. t.Errorf("hostinfo.PushDeviceToken=%q, want %q", got, want)
  74. }
  75. }
  76. type whoIsBackend struct {
  77. whoIs func(ipp netip.AddrPort) (n tailcfg.NodeView, u tailcfg.UserProfile, ok bool)
  78. peerCaps map[netip.Addr]tailcfg.PeerCapMap
  79. }
  80. func (b whoIsBackend) WhoIs(ipp netip.AddrPort) (n tailcfg.NodeView, u tailcfg.UserProfile, ok bool) {
  81. return b.whoIs(ipp)
  82. }
  83. func (b whoIsBackend) PeerCaps(ip netip.Addr) tailcfg.PeerCapMap {
  84. return b.peerCaps[ip]
  85. }
  86. // Tests that the WhoIs handler accepts either IPs or IP:ports.
  87. //
  88. // From https://github.com/tailscale/tailscale/pull/9714 (a PR that is effectively a bug report)
  89. func TestWhoIsJustIP(t *testing.T) {
  90. h := &Handler{
  91. PermitRead: true,
  92. }
  93. for _, input := range []string{"100.101.102.103", "127.0.0.1:123"} {
  94. rec := httptest.NewRecorder()
  95. t.Run(input, func(t *testing.T) {
  96. b := whoIsBackend{
  97. whoIs: func(ipp netip.AddrPort) (n tailcfg.NodeView, u tailcfg.UserProfile, ok bool) {
  98. if !strings.Contains(input, ":") {
  99. want := netip.MustParseAddrPort("100.101.102.103:0")
  100. if ipp != want {
  101. t.Fatalf("backend called with %v; want %v", ipp, want)
  102. }
  103. }
  104. return (&tailcfg.Node{
  105. ID: 123,
  106. Addresses: []netip.Prefix{
  107. netip.MustParsePrefix("100.101.102.103/32"),
  108. },
  109. }).View(),
  110. tailcfg.UserProfile{ID: 456, DisplayName: "foo"},
  111. true
  112. },
  113. peerCaps: map[netip.Addr]tailcfg.PeerCapMap{
  114. netip.MustParseAddr("100.101.102.103"): map[tailcfg.PeerCapability][]tailcfg.RawMessage{
  115. "foo": {`"bar"`},
  116. },
  117. },
  118. }
  119. h.serveWhoIsWithBackend(rec, httptest.NewRequest("GET", "/v0/whois?addr="+url.QueryEscape(input), nil), b)
  120. var res apitype.WhoIsResponse
  121. if err := json.Unmarshal(rec.Body.Bytes(), &res); err != nil {
  122. t.Fatal(err)
  123. }
  124. if got, want := res.Node.ID, tailcfg.NodeID(123); got != want {
  125. t.Errorf("res.Node.ID=%v, want %v", got, want)
  126. }
  127. if got, want := res.UserProfile.DisplayName, "foo"; got != want {
  128. t.Errorf("res.UserProfile.DisplayName=%q, want %q", got, want)
  129. }
  130. if got, want := len(res.CapMap), 1; got != want {
  131. t.Errorf("capmap size=%v, want %v", got, want)
  132. }
  133. })
  134. }
  135. }