notice.go 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. /*
  2. * Copyright (c) 2015, Psiphon Inc.
  3. * All rights reserved.
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. *
  18. */
  19. package psiphon
  20. import (
  21. "bytes"
  22. "encoding/json"
  23. "fmt"
  24. "io"
  25. "log"
  26. "os"
  27. "sync"
  28. "time"
  29. )
  30. var noticeLoggerMutex sync.Mutex
  31. var noticeLogger = log.New(os.Stderr, "", 0)
  32. // SetNoticeOutput sets a target writer to receive notices. By default,
  33. // notices are written to stderr.
  34. //
  35. // Notices are encoded in JSON. Here's an example:
  36. //
  37. // {"data":{"message":"shutdown operate tunnel"},"noticeType":"Info","showUser":false,"timestamp":"2015-01-28T17:35:13Z"}
  38. //
  39. // All notices have the following fields:
  40. // - "noticeType": the type of notice, which indicates the meaning of the notice along with what's in the data payload.
  41. // - "data": additional structured data payload. For example, the "ListeningSocksProxyPort" notice type has a "port" integer
  42. // data in its payload.
  43. // - "showUser": whether the information should be displayed to the user. For example, this flag is set for "SocksProxyPortInUse"
  44. // as the user should be informed that their configured choice of listening port could not be used. Core clients should
  45. // anticipate that the core will add additional "showUser"=true notices in the future and emit at least the raw notice.
  46. // - "timestamp": UTC timezone, RFC3339 format timestamp for notice event
  47. //
  48. // See the Notice* functions for details on each notice meaning and payload.
  49. //
  50. func SetNoticeOutput(output io.Writer) {
  51. noticeLoggerMutex.Lock()
  52. defer noticeLoggerMutex.Unlock()
  53. noticeLogger = log.New(output, "", 0)
  54. }
  55. // outputNotice encodes a notice in JSON and writes it to the output writer.
  56. func outputNotice(noticeType string, showUser bool, args ...interface{}) {
  57. obj := make(map[string]interface{})
  58. noticeData := make(map[string]interface{})
  59. obj["noticeType"] = noticeType
  60. obj["showUser"] = showUser
  61. obj["data"] = noticeData
  62. obj["timestamp"] = time.Now().UTC().Format(time.RFC3339)
  63. for i := 0; i < len(args)-1; i += 2 {
  64. name, ok := args[i].(string)
  65. value := args[i+1]
  66. if ok {
  67. noticeData[name] = value
  68. }
  69. }
  70. encodedJson, err := json.Marshal(obj)
  71. var output string
  72. if err == nil {
  73. output = string(encodedJson)
  74. } else {
  75. output = fmt.Sprintf("{\"Alert\":{\"message\":\"%s\"}}", ContextError(err))
  76. }
  77. noticeLoggerMutex.Lock()
  78. defer noticeLoggerMutex.Unlock()
  79. noticeLogger.Print(output)
  80. }
  81. // NoticeInfo is an informational message
  82. func NoticeInfo(format string, args ...interface{}) {
  83. outputNotice("Info", false, "message", fmt.Sprintf(format, args...))
  84. }
  85. // NoticeInfo is an alert message; typically a recoverable error condition
  86. func NoticeAlert(format string, args ...interface{}) {
  87. outputNotice("Alert", false, "message", fmt.Sprintf(format, args...))
  88. }
  89. // NoticeInfo is an error message; typically an unrecoverable error condition
  90. func NoticeError(format string, args ...interface{}) {
  91. outputNotice("Error", true, "message", fmt.Sprintf(format, args...))
  92. }
  93. // NoticeCoreVersion is the version string of the core
  94. func NoticeCoreVersion(version string) {
  95. outputNotice("CoreVersion", false, "version", version)
  96. }
  97. // NoticeCandidateServers is how many possible servers are available for the selected region and protocol
  98. func NoticeCandidateServers(region, protocol string, count int) {
  99. outputNotice("CandidateServers", false, "region", region, "protocol", protocol, "count", count)
  100. }
  101. // NoticeAvailableEgressRegions is what regions are available for egress from
  102. func NoticeAvailableEgressRegions(regions []string) {
  103. outputNotice("AvailableEgressRegions", false, "regions", regions)
  104. }
  105. // NoticeConnectingServer is details on a connection attempt
  106. func NoticeConnectingServer(ipAddress, region, protocol, frontingAddress string) {
  107. outputNotice("ConnectingServer", false, "ipAddress", ipAddress, "region",
  108. region, "protocol", protocol, "frontingAddress", frontingAddress)
  109. }
  110. // NoticeActiveTunnel is a successful connection that is used as an active tunnel for port forwarding
  111. func NoticeActiveTunnel(ipAddress string) {
  112. outputNotice("ActiveTunnel", false, "ipAddress", ipAddress)
  113. }
  114. // NoticeSocksProxyPortInUse is a failure to use the configured LocalSocksProxyPort
  115. func NoticeSocksProxyPortInUse(port int) {
  116. outputNotice("SocksProxyPortInUse", true, "port", port)
  117. }
  118. // NoticeListeningSocksProxyPort is the selected port for the listening local SOCKS proxy
  119. func NoticeListeningSocksProxyPort(port int) {
  120. outputNotice("ListeningSocksProxyPort", false, "port", port)
  121. }
  122. // NoticeSocksProxyPortInUse is a failure to use the configured LocalHttpProxyPort
  123. func NoticeHttpProxyPortInUse(port int) {
  124. outputNotice("HttpProxyPortInUse", true, "port", port)
  125. }
  126. // NoticeListeningSocksProxyPort is the selected port for the listening local HTTP proxy
  127. func NoticeListeningHttpProxyPort(port int) {
  128. outputNotice("ListeningHttpProxyPort", false, "port", port)
  129. }
  130. // NoticeClientUpgradeAvailable is an available client upgrade, as per the handshake. The
  131. // client should download and install an upgrade.
  132. func NoticeClientUpgradeAvailable(version string) {
  133. outputNotice("ClientUpgradeAvailable", false, "version", version)
  134. }
  135. // NoticeClientUpgradeAvailable is a sponsor homepage, as per the handshake. The client
  136. // should display the sponsor's homepage.
  137. func NoticeHomepage(url string) {
  138. outputNotice("Homepage", false, "url", url)
  139. }
  140. // NoticeTunnels is how many active tunnels are available. The client should use this to
  141. // determine connecting/unexpected disconnect state transitions. When count is 0, the core is
  142. // disconnected; when count > 1, the core is connected.
  143. func NoticeTunnels(count int) {
  144. outputNotice("Tunnels", false, "count", count)
  145. }
  146. // NoticeUntunneled indicates than an address has been classified as untunneled and is being
  147. // accessed directly.
  148. //
  149. // Note: "address" should remain private; this notice should only be used for alerting
  150. // users, not for diagnostics logs.
  151. //
  152. func NoticeUntunneled(address string) {
  153. outputNotice("Untunneled", true, "address", address)
  154. }
  155. // NoticeSplitTunnelRegion reports that split tunnel is on for the given region.
  156. func NoticeSplitTunnelRegion(region string) {
  157. outputNotice("SplitTunnelRegion", true, "region", region)
  158. }
  159. // NoticeUpstreamProxyError reports an error when connecting to an upstream proxy. The
  160. // user may have input, for example, an incorrect address or incorrect credentials.
  161. func NoticeUpstreamProxyError(err error) {
  162. outputNotice("UpstreamProxyError", true, "message", fmt.Sprintf("%s", err))
  163. }
  164. // NoticeClientUpgradeDownloaded indicates that a client upgrade download
  165. // is complete and available at the destination specified.
  166. func NoticeClientUpgradeDownloaded(filename string) {
  167. outputNotice("ClientUpgradeDownloaded", false, "filename", filename)
  168. }
  169. type noticeObject struct {
  170. NoticeType string `json:"noticeType"`
  171. Data json.RawMessage `json:"data"`
  172. Timestamp string `json:"timestamp"`
  173. }
  174. // GetNotice receives a JSON encoded object and attempts to parse it as a Notice.
  175. // The type is returned as a string and the payload as a generic map.
  176. func GetNotice(notice []byte) (
  177. noticeType string, payload map[string]interface{}, err error) {
  178. var object noticeObject
  179. err = json.Unmarshal(notice, &object)
  180. if err != nil {
  181. return "", nil, err
  182. }
  183. var objectPayload interface{}
  184. err = json.Unmarshal(object.Data, &objectPayload)
  185. if err != nil {
  186. return "", nil, err
  187. }
  188. return object.NoticeType, objectPayload.(map[string]interface{}), nil
  189. }
  190. // NoticeReceiver consumes a notice input stream and invokes a callback function
  191. // for each discrete JSON notice object byte sequence.
  192. type NoticeReceiver struct {
  193. mutex sync.Mutex
  194. buffer []byte
  195. callback func([]byte)
  196. }
  197. // NewNoticeReceiver initializes a new NoticeReceiver
  198. func NewNoticeReceiver(callback func([]byte)) *NoticeReceiver {
  199. return &NoticeReceiver{callback: callback}
  200. }
  201. // Write implements io.Writer.
  202. func (receiver *NoticeReceiver) Write(p []byte) (n int, err error) {
  203. receiver.mutex.Lock()
  204. defer receiver.mutex.Unlock()
  205. receiver.buffer = append(receiver.buffer, p...)
  206. index := bytes.Index(receiver.buffer, []byte("\n"))
  207. if index == -1 {
  208. return len(p), nil
  209. }
  210. notice := receiver.buffer[:index]
  211. receiver.buffer = receiver.buffer[index+1:]
  212. receiver.callback(notice)
  213. return len(p), nil
  214. }
  215. // NewNoticeConsoleRewriter consumes JSON-format notice input and parses each
  216. // notice and rewrites in a more human-readable format more suitable for
  217. // console output. The data payload field is left as JSON.
  218. func NewNoticeConsoleRewriter(writer io.Writer) *NoticeReceiver {
  219. return NewNoticeReceiver(func(notice []byte) {
  220. var object noticeObject
  221. _ = json.Unmarshal(notice, &object)
  222. fmt.Fprintf(
  223. writer,
  224. "%s %s %s\n",
  225. object.Timestamp,
  226. object.NoticeType,
  227. string(object.Data))
  228. })
  229. }