2
0

message.go 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. // Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package ipn
  5. import (
  6. "bytes"
  7. "context"
  8. "encoding/binary"
  9. "encoding/json"
  10. "errors"
  11. "fmt"
  12. "io"
  13. "log"
  14. "tailscale.com/envknob"
  15. "tailscale.com/tailcfg"
  16. "tailscale.com/types/logger"
  17. "tailscale.com/types/structs"
  18. "tailscale.com/version"
  19. )
  20. type readOnlyContextKey struct{}
  21. // IsReadonlyContext reports whether ctx is a read-only context, as currently used
  22. // by Unix non-root users running the "tailscale" CLI command. They can run "status",
  23. // but not much else.
  24. func IsReadonlyContext(ctx context.Context) bool {
  25. return ctx.Value(readOnlyContextKey{}) != nil
  26. }
  27. // ReadonlyContextOf returns ctx wrapped with a context value that
  28. // will make IsReadonlyContext reports true.
  29. func ReadonlyContextOf(ctx context.Context) context.Context {
  30. if IsReadonlyContext(ctx) {
  31. return ctx
  32. }
  33. return context.WithValue(ctx, readOnlyContextKey{}, readOnlyContextKey{})
  34. }
  35. var jsonEscapedZero = []byte(`\u0000`)
  36. type NoArgs struct{}
  37. type StartArgs struct {
  38. Opts Options
  39. }
  40. type SetPrefsArgs struct {
  41. New *Prefs
  42. }
  43. // Command is a command message that is JSON encoded and sent by a
  44. // frontend to a backend.
  45. type Command struct {
  46. _ structs.Incomparable
  47. // Version is the binary version of the frontend (the client).
  48. Version string
  49. // AllowVersionSkew controls whether it's permitted for the
  50. // client and server to have a different version. The default
  51. // (false) means to be strict.
  52. AllowVersionSkew bool
  53. // Exactly one of the following must be non-nil.
  54. Quit *NoArgs
  55. Start *StartArgs
  56. StartLoginInteractive *NoArgs
  57. Login *tailcfg.Oauth2Token
  58. Logout *NoArgs
  59. SetPrefs *SetPrefsArgs
  60. RequestEngineStatus *NoArgs
  61. RequestStatus *NoArgs
  62. }
  63. type BackendServer struct {
  64. logf logger.Logf
  65. b Backend // the Backend we are serving up
  66. sendNotifyMsg func(Notify) // send a notification message
  67. GotQuit bool // a Quit command was received
  68. }
  69. // NewBackendServer creates a new BackendServer using b.
  70. //
  71. // If sendNotifyMsg is non-nil, it additionally sets the Backend's
  72. // notification callback to call the func with ipn.Notify messages in
  73. // JSON form. If nil, it does not change the notification callback.
  74. func NewBackendServer(logf logger.Logf, b Backend, sendNotifyMsg func(Notify)) *BackendServer {
  75. bs := &BackendServer{
  76. logf: logf,
  77. b: b,
  78. sendNotifyMsg: sendNotifyMsg,
  79. }
  80. // b may be nil if the BackendServer is being created just to
  81. // encapsulate and send an error message.
  82. if sendNotifyMsg != nil && b != nil {
  83. b.SetNotifyCallback(bs.send)
  84. }
  85. return bs
  86. }
  87. func (bs *BackendServer) send(n Notify) {
  88. if bs.sendNotifyMsg == nil {
  89. return
  90. }
  91. n.Version = ipcVersion
  92. bs.sendNotifyMsg(n)
  93. }
  94. func (bs *BackendServer) SendErrorMessage(msg string) {
  95. bs.send(Notify{ErrMessage: &msg})
  96. }
  97. // SendInUseOtherUserErrorMessage sends a Notify message to the client that
  98. // both sets the state to 'InUseOtherUser' and sets the associated reason
  99. // to msg.
  100. func (bs *BackendServer) SendInUseOtherUserErrorMessage(msg string) {
  101. inUse := InUseOtherUser
  102. bs.send(Notify{
  103. State: &inUse,
  104. ErrMessage: &msg,
  105. })
  106. }
  107. // GotCommandMsg parses the incoming message b as a JSON Command and
  108. // calls GotCommand with it.
  109. func (bs *BackendServer) GotCommandMsg(ctx context.Context, b []byte) error {
  110. cmd := &Command{}
  111. if len(b) == 0 {
  112. return nil
  113. }
  114. if err := json.Unmarshal(b, cmd); err != nil {
  115. return err
  116. }
  117. return bs.GotCommand(ctx, cmd)
  118. }
  119. // ErrMsgPermissionDenied is the Notify.ErrMessage value used an
  120. // operation was done from a user/context that didn't have permission.
  121. const ErrMsgPermissionDenied = "permission denied"
  122. func (bs *BackendServer) GotCommand(ctx context.Context, cmd *Command) error {
  123. if cmd.Version != ipcVersion && !cmd.AllowVersionSkew {
  124. vs := fmt.Sprintf("GotCommand: Version mismatch! frontend=%#v backend=%#v",
  125. cmd.Version, ipcVersion)
  126. bs.logf("%s", vs)
  127. // ignore the command, but send a message back to the
  128. // caller so it can realize the version mismatch too.
  129. // We don't want to exit because it might cause a crash
  130. // loop, and restarting won't fix the problem.
  131. bs.send(Notify{
  132. ErrMessage: &vs,
  133. })
  134. return nil
  135. }
  136. // TODO(bradfitz): finish plumbing context down to all the methods below;
  137. // currently we just check for read-only contexts in this method and
  138. // then never use contexts again.
  139. // Actions permitted with a read-only context:
  140. if c := cmd.RequestEngineStatus; c != nil {
  141. bs.b.RequestEngineStatus()
  142. return nil
  143. }
  144. if IsReadonlyContext(ctx) {
  145. msg := ErrMsgPermissionDenied
  146. bs.send(Notify{ErrMessage: &msg})
  147. return nil
  148. }
  149. if cmd.Quit != nil {
  150. bs.GotQuit = true
  151. return errors.New("Quit command received")
  152. } else if c := cmd.Start; c != nil {
  153. opts := c.Opts
  154. return bs.b.Start(opts)
  155. } else if c := cmd.StartLoginInteractive; c != nil {
  156. bs.b.StartLoginInteractive()
  157. return nil
  158. } else if c := cmd.Login; c != nil {
  159. bs.b.Login(c)
  160. return nil
  161. } else if c := cmd.Logout; c != nil {
  162. bs.b.Logout()
  163. return nil
  164. } else if c := cmd.SetPrefs; c != nil {
  165. bs.b.SetPrefs(c.New)
  166. return nil
  167. }
  168. return fmt.Errorf("BackendServer.Do: no command specified")
  169. }
  170. type BackendClient struct {
  171. logf logger.Logf
  172. sendCommandMsg func(jsonb []byte)
  173. notify func(Notify)
  174. // AllowVersionSkew controls whether to allow mismatched
  175. // frontend & backend versions.
  176. AllowVersionSkew bool
  177. }
  178. func NewBackendClient(logf logger.Logf, sendCommandMsg func(jsonb []byte)) *BackendClient {
  179. return &BackendClient{
  180. logf: logf,
  181. sendCommandMsg: sendCommandMsg,
  182. }
  183. }
  184. // IPCVersion returns version.Long usually, unless TS_DEBUG_FAKE_IPC_VERSION is
  185. // set, in which it contains that value. This is only used for weird development
  186. // cases when testing mismatched versions and you want the client to act like it's
  187. // compatible with the server.
  188. func IPCVersion() string {
  189. if v := envknob.String("TS_DEBUG_FAKE_IPC_VERSION"); v != "" {
  190. return v
  191. }
  192. return version.Long
  193. }
  194. var ipcVersion = IPCVersion()
  195. func (bc *BackendClient) GotNotifyMsg(b []byte) {
  196. if len(b) == 0 {
  197. // not interesting
  198. return
  199. }
  200. if bytes.Contains(b, jsonEscapedZero) {
  201. log.Printf("[unexpected] zero byte in BackendClient.GotNotifyMsg message: %q", b)
  202. }
  203. n := Notify{}
  204. if err := json.Unmarshal(b, &n); err != nil {
  205. log.Fatalf("BackendClient.Notify: cannot decode message (length=%d, %#q): %v", len(b), b, err)
  206. }
  207. if n.Version != ipcVersion && !bc.AllowVersionSkew {
  208. vs := fmt.Sprintf("GotNotify: Version mismatch! frontend=%#v backend=%#v",
  209. ipcVersion, n.Version)
  210. bc.logf("%s", vs)
  211. // delete anything in the notification except the version,
  212. // to prevent incorrect operation.
  213. n = Notify{
  214. Version: n.Version,
  215. ErrMessage: &vs,
  216. }
  217. }
  218. if bc.notify != nil {
  219. bc.notify(n)
  220. }
  221. }
  222. func (bc *BackendClient) send(cmd Command) {
  223. cmd.Version = ipcVersion
  224. b, err := json.Marshal(cmd)
  225. if err != nil {
  226. log.Fatalf("Failed json.Marshal(cmd): %v\n", err)
  227. }
  228. if bytes.Contains(b, jsonEscapedZero) {
  229. log.Printf("[unexpected] zero byte in BackendClient.send command")
  230. }
  231. bc.sendCommandMsg(b)
  232. }
  233. func (bc *BackendClient) SetNotifyCallback(fn func(Notify)) {
  234. bc.notify = fn
  235. }
  236. func (bc *BackendClient) Quit() error {
  237. bc.send(Command{Quit: &NoArgs{}})
  238. return nil
  239. }
  240. func (bc *BackendClient) Start(opts Options) error {
  241. bc.send(Command{Start: &StartArgs{Opts: opts}})
  242. return nil // remote Start() errors must be handled remotely
  243. }
  244. func (bc *BackendClient) StartLoginInteractive() {
  245. bc.send(Command{StartLoginInteractive: &NoArgs{}})
  246. }
  247. func (bc *BackendClient) Login(token *tailcfg.Oauth2Token) {
  248. bc.send(Command{Login: token})
  249. }
  250. func (bc *BackendClient) Logout() {
  251. bc.send(Command{Logout: &NoArgs{}})
  252. }
  253. func (bc *BackendClient) SetPrefs(new *Prefs) {
  254. bc.send(Command{SetPrefs: &SetPrefsArgs{New: new}})
  255. }
  256. func (bc *BackendClient) RequestEngineStatus() {
  257. bc.send(Command{RequestEngineStatus: &NoArgs{}})
  258. }
  259. func (bc *BackendClient) RequestStatus() {
  260. bc.send(Command{AllowVersionSkew: true, RequestStatus: &NoArgs{}})
  261. }
  262. // MaxMessageSize is the maximum message size, in bytes.
  263. const MaxMessageSize = 10 << 20
  264. // TODO(apenwarr): incremental json decode? That would let us avoid
  265. // storing the whole byte array uselessly in RAM.
  266. func ReadMsg(r io.Reader) ([]byte, error) {
  267. cb := make([]byte, 4)
  268. _, err := io.ReadFull(r, cb)
  269. if err != nil {
  270. return nil, err
  271. }
  272. n := binary.LittleEndian.Uint32(cb)
  273. if n > MaxMessageSize {
  274. return nil, fmt.Errorf("ipn.Read: message too large: %v bytes", n)
  275. }
  276. b := make([]byte, n)
  277. nn, err := io.ReadFull(r, b)
  278. if err != nil {
  279. return nil, err
  280. }
  281. if nn != int(n) {
  282. return nil, fmt.Errorf("ipn.Read: expected %v bytes, got %v", n, nn)
  283. }
  284. return b, nil
  285. }
  286. func WriteMsg(w io.Writer, b []byte) error {
  287. // TODO(apenwarr): incremental json encode? That would save RAM, at the
  288. // expense of having to encode once so that we can produce the initial byte
  289. // count.
  290. // TODO(bradfitz): this does two writes to w, which likely
  291. // does two writes on the wire, two frame generations, etc. We
  292. // should take a concrete buffered type, or use a sync.Pool to
  293. // allocate a buf and do one write.
  294. cb := make([]byte, 4)
  295. if len(b) > MaxMessageSize {
  296. return fmt.Errorf("ipn.Write: message too large: %v bytes", len(b))
  297. }
  298. binary.LittleEndian.PutUint32(cb, uint32(len(b)))
  299. n, err := w.Write(cb)
  300. if err != nil {
  301. return err
  302. }
  303. if n != 4 {
  304. return fmt.Errorf("ipn.Write: short write: %v bytes (wanted 4)", n)
  305. }
  306. n, err = w.Write(b)
  307. if err != nil {
  308. return err
  309. }
  310. if n != len(b) {
  311. return fmt.Errorf("ipn.Write: short write: %v bytes (wanted %v)", n, len(b))
  312. }
  313. return nil
  314. }