notice.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  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. "sort"
  28. "strings"
  29. "sync"
  30. "sync/atomic"
  31. "time"
  32. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
  33. )
  34. var noticeLoggerMutex sync.Mutex
  35. var noticeLogger = log.New(os.Stderr, "", 0)
  36. var noticeLogDiagnostics = int32(0)
  37. // SetEmitDiagnosticNotices toggles whether diagnostic notices
  38. // are emitted. Diagnostic notices contain potentially sensitive
  39. // circumvention network information; only enable this in environments
  40. // where notices are handled securely (for example, don't include these
  41. // notices in log files which users could post to public forums).
  42. func SetEmitDiagnosticNotices(enable bool) {
  43. if enable {
  44. atomic.StoreInt32(&noticeLogDiagnostics, 1)
  45. } else {
  46. atomic.StoreInt32(&noticeLogDiagnostics, 0)
  47. }
  48. }
  49. // GetEmitDiagnoticNotices returns the current state
  50. // of emitting diagnostic notices.
  51. func GetEmitDiagnoticNotices() bool {
  52. return atomic.LoadInt32(&noticeLogDiagnostics) == 1
  53. }
  54. // SetNoticeOutput sets a target writer to receive notices. By default,
  55. // notices are written to stderr.
  56. //
  57. // Notices are encoded in JSON. Here's an example:
  58. //
  59. // {"data":{"message":"shutdown operate tunnel"},"noticeType":"Info","showUser":false,"timestamp":"2015-01-28T17:35:13Z"}
  60. //
  61. // All notices have the following fields:
  62. // - "noticeType": the type of notice, which indicates the meaning of the notice along with what's in the data payload.
  63. // - "data": additional structured data payload. For example, the "ListeningSocksProxyPort" notice type has a "port" integer
  64. // data in its payload.
  65. // - "showUser": whether the information should be displayed to the user. For example, this flag is set for "SocksProxyPortInUse"
  66. // as the user should be informed that their configured choice of listening port could not be used. Core clients should
  67. // anticipate that the core will add additional "showUser"=true notices in the future and emit at least the raw notice.
  68. // - "timestamp": UTC timezone, RFC3339 format timestamp for notice event
  69. //
  70. // See the Notice* functions for details on each notice meaning and payload.
  71. //
  72. func SetNoticeOutput(output io.Writer) {
  73. noticeLoggerMutex.Lock()
  74. defer noticeLoggerMutex.Unlock()
  75. noticeLogger = log.New(output, "", 0)
  76. }
  77. const (
  78. noticeIsDiagnostic = 1
  79. noticeShowUser = 2
  80. )
  81. // outputNotice encodes a notice in JSON and writes it to the output writer.
  82. func outputNotice(noticeType string, noticeFlags uint32, args ...interface{}) {
  83. if (noticeFlags&noticeIsDiagnostic != 0) && !GetEmitDiagnoticNotices() {
  84. return
  85. }
  86. obj := make(map[string]interface{})
  87. noticeData := make(map[string]interface{})
  88. obj["noticeType"] = noticeType
  89. obj["showUser"] = (noticeFlags&noticeShowUser != 0)
  90. obj["data"] = noticeData
  91. obj["timestamp"] = time.Now().UTC().Format(time.RFC3339)
  92. for i := 0; i < len(args)-1; i += 2 {
  93. name, ok := args[i].(string)
  94. value := args[i+1]
  95. if ok {
  96. noticeData[name] = value
  97. }
  98. }
  99. encodedJson, err := json.Marshal(obj)
  100. var output string
  101. if err == nil {
  102. output = string(encodedJson)
  103. } else {
  104. output = fmt.Sprintf("{\"Alert\":{\"message\":\"%s\"}}", common.ContextError(err))
  105. }
  106. noticeLoggerMutex.Lock()
  107. defer noticeLoggerMutex.Unlock()
  108. noticeLogger.Print(output)
  109. }
  110. // NoticeInfo is an informational message
  111. func NoticeInfo(format string, args ...interface{}) {
  112. outputNotice("Info", noticeIsDiagnostic, "message", fmt.Sprintf(format, args...))
  113. }
  114. // NoticeAlert is an alert message; typically a recoverable error condition
  115. func NoticeAlert(format string, args ...interface{}) {
  116. outputNotice("Alert", noticeIsDiagnostic, "message", fmt.Sprintf(format, args...))
  117. }
  118. // NoticeError is an error message; typically an unrecoverable error condition
  119. func NoticeError(format string, args ...interface{}) {
  120. outputNotice("Error", noticeIsDiagnostic, "message", fmt.Sprintf(format, args...))
  121. }
  122. // NoticeCandidateServers is how many possible servers are available for the selected region and protocol
  123. func NoticeCandidateServers(region, protocol string, count int) {
  124. outputNotice("CandidateServers", 0, "region", region, "protocol", protocol, "count", count)
  125. }
  126. // NoticeAvailableEgressRegions is what regions are available for egress from.
  127. // Consecutive reports of the same list of regions are suppressed.
  128. func NoticeAvailableEgressRegions(regions []string) {
  129. sortedRegions := append([]string(nil), regions...)
  130. sort.Strings(sortedRegions)
  131. repetitionMessage := strings.Join(sortedRegions, "")
  132. outputRepetitiveNotice(
  133. "AvailableEgressRegions", repetitionMessage, 0,
  134. "AvailableEgressRegions", 0, "regions", sortedRegions)
  135. }
  136. // NoticeConnectingServer is details on a connection attempt
  137. func NoticeConnectingServer(ipAddress, region, protocol, directTCPDialAddress string, meekConfig *MeekConfig) {
  138. if meekConfig == nil {
  139. outputNotice("ConnectingServer", noticeIsDiagnostic,
  140. "ipAddress", ipAddress,
  141. "region", region,
  142. "protocol", protocol,
  143. "directTCPDialAddress", directTCPDialAddress)
  144. } else {
  145. outputNotice("ConnectingServer", noticeIsDiagnostic,
  146. "ipAddress", ipAddress,
  147. "region", region,
  148. "protocol", protocol,
  149. "meekDialAddress", meekConfig.DialAddress,
  150. "meekUseHTTPS", meekConfig.UseHTTPS,
  151. "meekSNIServerName", meekConfig.SNIServerName,
  152. "meekHostHeader", meekConfig.HostHeader,
  153. "meekTransformedHostName", meekConfig.TransformedHostName)
  154. }
  155. }
  156. // NoticeActiveTunnel is a successful connection that is used as an active tunnel for port forwarding
  157. func NoticeActiveTunnel(ipAddress, protocol string) {
  158. outputNotice("ActiveTunnel", noticeIsDiagnostic, "ipAddress", ipAddress, "protocol", protocol)
  159. }
  160. // NoticeSocksProxyPortInUse is a failure to use the configured LocalSocksProxyPort
  161. func NoticeSocksProxyPortInUse(port int) {
  162. outputNotice("SocksProxyPortInUse", noticeShowUser, "port", port)
  163. }
  164. // NoticeListeningSocksProxyPort is the selected port for the listening local SOCKS proxy
  165. func NoticeListeningSocksProxyPort(port int) {
  166. outputNotice("ListeningSocksProxyPort", 0, "port", port)
  167. }
  168. // NoticeSocksProxyPortInUse is a failure to use the configured LocalHttpProxyPort
  169. func NoticeHttpProxyPortInUse(port int) {
  170. outputNotice("HttpProxyPortInUse", noticeShowUser, "port", port)
  171. }
  172. // NoticeListeningSocksProxyPort is the selected port for the listening local HTTP proxy
  173. func NoticeListeningHttpProxyPort(port int) {
  174. outputNotice("ListeningHttpProxyPort", 0, "port", port)
  175. }
  176. // NoticeClientUpgradeAvailable is an available client upgrade, as per the handshake. The
  177. // client should download and install an upgrade.
  178. func NoticeClientUpgradeAvailable(version string) {
  179. outputNotice("ClientUpgradeAvailable", 0, "version", version)
  180. }
  181. // NoticeClientIsLatestVersion reports that an upgrade check was made and the client
  182. // is already the latest version. availableVersion is the version available for download,
  183. // if known.
  184. func NoticeClientIsLatestVersion(availableVersion string) {
  185. outputNotice("ClientIsLatestVersion", 0, "availableVersion", availableVersion)
  186. }
  187. // NoticeHomepage is a sponsor homepage, as per the handshake. The client
  188. // should display the sponsor's homepage.
  189. func NoticeHomepage(url string) {
  190. outputNotice("Homepage", 0, "url", url)
  191. }
  192. // NoticeClientVerificationRequired indicates that client verification is required, as
  193. // indicated by the handshake. The client should submit a client verification payload.
  194. // Empty nonce is allowed, if ttlSeconds is 0 the client should not send verification
  195. // payload to the server. If resetCache is set the client must always perform a new
  196. // verification and update its cache
  197. func NoticeClientVerificationRequired(nonce string, ttlSeconds int, resetCache bool) {
  198. outputNotice("ClientVerificationRequired", 0, "nonce", nonce, "ttlSeconds", ttlSeconds, "resetCache", resetCache)
  199. }
  200. // NoticeClientRegion is the client's region, as determined by the server and
  201. // reported to the client in the handshake.
  202. func NoticeClientRegion(region string) {
  203. outputNotice("ClientRegion", 0, "region", region)
  204. }
  205. // NoticeTunnels is how many active tunnels are available. The client should use this to
  206. // determine connecting/unexpected disconnect state transitions. When count is 0, the core is
  207. // disconnected; when count > 1, the core is connected.
  208. func NoticeTunnels(count int) {
  209. outputNotice("Tunnels", 0, "count", count)
  210. }
  211. // NoticeSessionId is the session ID used across all tunnels established by the controller.
  212. func NoticeSessionId(sessionId string) {
  213. outputNotice("SessionId", noticeIsDiagnostic, "sessionId", sessionId)
  214. }
  215. func NoticeImpairedProtocolClassification(impairedProtocolClassification map[string]int) {
  216. outputNotice("ImpairedProtocolClassification", noticeIsDiagnostic,
  217. "classification", impairedProtocolClassification)
  218. }
  219. // NoticeUntunneled indicates than an address has been classified as untunneled and is being
  220. // accessed directly.
  221. //
  222. // Note: "address" should remain private; this notice should only be used for alerting
  223. // users, not for diagnostics logs.
  224. //
  225. func NoticeUntunneled(address string) {
  226. outputNotice("Untunneled", noticeShowUser, "address", address)
  227. }
  228. // NoticeSplitTunnelRegion reports that split tunnel is on for the given region.
  229. func NoticeSplitTunnelRegion(region string) {
  230. outputNotice("SplitTunnelRegion", noticeShowUser, "region", region)
  231. }
  232. // NoticeUpstreamProxyError reports an error when connecting to an upstream proxy. The
  233. // user may have input, for example, an incorrect address or incorrect credentials.
  234. func NoticeUpstreamProxyError(err error) {
  235. outputNotice("UpstreamProxyError", noticeShowUser, "message", err.Error())
  236. }
  237. // NoticeClientUpgradeDownloadedBytes reports client upgrade download progress.
  238. func NoticeClientUpgradeDownloadedBytes(bytes int64) {
  239. outputNotice("ClientUpgradeDownloadedBytes", noticeIsDiagnostic, "bytes", bytes)
  240. }
  241. // NoticeClientUpgradeDownloaded indicates that a client upgrade download
  242. // is complete and available at the destination specified.
  243. func NoticeClientUpgradeDownloaded(filename string) {
  244. outputNotice("ClientUpgradeDownloaded", 0, "filename", filename)
  245. }
  246. // NoticeBytesTransferred reports how many tunneled bytes have been
  247. // transferred since the last NoticeBytesTransferred, for the tunnel
  248. // to the server at ipAddress.
  249. func NoticeBytesTransferred(ipAddress string, sent, received int64) {
  250. if GetEmitDiagnoticNotices() {
  251. outputNotice("BytesTransferred", noticeIsDiagnostic, "ipAddress", ipAddress, "sent", sent, "received", received)
  252. } else {
  253. // This case keeps the EmitBytesTransferred and EmitDiagnosticNotices config options independent
  254. outputNotice("BytesTransferred", 0, "sent", sent, "received", received)
  255. }
  256. }
  257. // NoticeTotalBytesTransferred reports how many tunneled bytes have been
  258. // transferred in total up to this point, for the tunnel to the server
  259. // at ipAddress.
  260. func NoticeTotalBytesTransferred(ipAddress string, sent, received int64) {
  261. if GetEmitDiagnoticNotices() {
  262. outputNotice("TotalBytesTransferred", noticeIsDiagnostic, "ipAddress", ipAddress, "sent", sent, "received", received)
  263. } else {
  264. // This case keeps the EmitBytesTransferred and EmitDiagnosticNotices config options independent
  265. outputNotice("TotalBytesTransferred", 0, "sent", sent, "received", received)
  266. }
  267. }
  268. // NoticeLocalProxyError reports a local proxy error message. Repetitive
  269. // errors for a given proxy type are suppressed.
  270. func NoticeLocalProxyError(proxyType string, err error) {
  271. // For repeats, only consider the base error message, which is
  272. // the root error that repeats (the full error often contains
  273. // different specific values, e.g., local port numbers, but
  274. // the same repeating root).
  275. // Assumes error format of common.ContextError.
  276. repetitionMessage := err.Error()
  277. index := strings.LastIndex(repetitionMessage, ": ")
  278. if index != -1 {
  279. repetitionMessage = repetitionMessage[index+2:]
  280. }
  281. outputRepetitiveNotice(
  282. "LocalProxyError"+proxyType, repetitionMessage, 1,
  283. "LocalProxyError", noticeIsDiagnostic, "message", err.Error())
  284. }
  285. // NoticeConnectedTunnelDialStats reports extra network details for tunnel connections that required extra configuration.
  286. func NoticeConnectedTunnelDialStats(ipAddress string, tunnelDialStats *TunnelDialStats) {
  287. outputNotice("ConnectedTunnelDialStats", noticeIsDiagnostic,
  288. "ipAddress", ipAddress,
  289. "upstreamProxyType", tunnelDialStats.UpstreamProxyType,
  290. "upstreamProxyCustomHeaderNames", strings.Join(tunnelDialStats.UpstreamProxyCustomHeaderNames, ","),
  291. "meekDialAddress", tunnelDialStats.MeekDialAddress,
  292. "meekDialAddress", tunnelDialStats.MeekDialAddress,
  293. "meekResolvedIPAddress", tunnelDialStats.MeekResolvedIPAddress,
  294. "meekSNIServerName", tunnelDialStats.MeekSNIServerName,
  295. "meekHostHeader", tunnelDialStats.MeekHostHeader,
  296. "meekTransformedHostName", tunnelDialStats.MeekTransformedHostName)
  297. }
  298. // NoticeBuildInfo reports build version info.
  299. func NoticeBuildInfo(buildDate, buildRepo, buildRev, goVersion, gomobileVersion string) {
  300. outputNotice("BuildInfo", 0,
  301. "buildDate", buildDate,
  302. "buildRepo", buildRepo,
  303. "buildRev", buildRev,
  304. "goVersion", goVersion,
  305. "gomobileVersion", gomobileVersion)
  306. }
  307. // NoticeExiting indicates that tunnel-core is exiting imminently.
  308. func NoticeExiting() {
  309. outputNotice("Exiting", 0)
  310. }
  311. // NoticeRemoteServerListDownloadedBytes reports remote server list download progress.
  312. func NoticeRemoteServerListDownloadedBytes(bytes int64) {
  313. outputNotice("RemoteServerListDownloadedBytes", noticeIsDiagnostic, "bytes", bytes)
  314. }
  315. // NoticeRemoteServerListDownloaded indicates that a remote server list download
  316. // completed successfully.
  317. func NoticeRemoteServerListDownloaded(filename string) {
  318. outputNotice("RemoteServerListDownloaded", noticeIsDiagnostic, "filename", filename)
  319. }
  320. func NoticeClientVerificationRequestCompleted(ipAddress string) {
  321. outputNotice("NoticeClientVerificationRequestCompleted", noticeIsDiagnostic, "ipAddress", ipAddress)
  322. }
  323. type repetitiveNoticeState struct {
  324. message string
  325. repeats int
  326. }
  327. var repetitiveNoticeMutex sync.Mutex
  328. var repetitiveNoticeStates = make(map[string]*repetitiveNoticeState)
  329. // outputRepetitiveNotice conditionally outputs a notice. Used for noticies which
  330. // often repeat in noisy bursts. For a repeat limit of N, the notice is emitted
  331. // with a "repeats" count on consecutive repeats up to the limit and then suppressed
  332. // until the repetitionMessage differs.
  333. func outputRepetitiveNotice(
  334. repetitionKey, repetitionMessage string, repeatLimit int,
  335. noticeType string, noticeFlags uint32, args ...interface{}) {
  336. repetitiveNoticeMutex.Lock()
  337. defer repetitiveNoticeMutex.Unlock()
  338. state, ok := repetitiveNoticeStates[repetitionKey]
  339. if !ok {
  340. state = new(repetitiveNoticeState)
  341. repetitiveNoticeStates[repetitionKey] = state
  342. }
  343. emit := true
  344. if repetitionMessage != state.message {
  345. state.message = repetitionMessage
  346. state.repeats = 0
  347. } else {
  348. state.repeats += 1
  349. if state.repeats > repeatLimit {
  350. emit = false
  351. }
  352. }
  353. if emit {
  354. if state.repeats > 0 {
  355. args = append(args, "repeats", state.repeats)
  356. }
  357. outputNotice(noticeType, noticeFlags, args...)
  358. }
  359. }
  360. type noticeObject struct {
  361. NoticeType string `json:"noticeType"`
  362. Data json.RawMessage `json:"data"`
  363. Timestamp string `json:"timestamp"`
  364. }
  365. // GetNotice receives a JSON encoded object and attempts to parse it as a Notice.
  366. // The type is returned as a string and the payload as a generic map.
  367. func GetNotice(notice []byte) (
  368. noticeType string, payload map[string]interface{}, err error) {
  369. var object noticeObject
  370. err = json.Unmarshal(notice, &object)
  371. if err != nil {
  372. return "", nil, err
  373. }
  374. var objectPayload interface{}
  375. err = json.Unmarshal(object.Data, &objectPayload)
  376. if err != nil {
  377. return "", nil, err
  378. }
  379. return object.NoticeType, objectPayload.(map[string]interface{}), nil
  380. }
  381. // NoticeReceiver consumes a notice input stream and invokes a callback function
  382. // for each discrete JSON notice object byte sequence.
  383. type NoticeReceiver struct {
  384. mutex sync.Mutex
  385. buffer []byte
  386. callback func([]byte)
  387. }
  388. // NewNoticeReceiver initializes a new NoticeReceiver
  389. func NewNoticeReceiver(callback func([]byte)) *NoticeReceiver {
  390. return &NoticeReceiver{callback: callback}
  391. }
  392. // Write implements io.Writer.
  393. func (receiver *NoticeReceiver) Write(p []byte) (n int, err error) {
  394. receiver.mutex.Lock()
  395. defer receiver.mutex.Unlock()
  396. receiver.buffer = append(receiver.buffer, p...)
  397. index := bytes.Index(receiver.buffer, []byte("\n"))
  398. if index == -1 {
  399. return len(p), nil
  400. }
  401. notice := receiver.buffer[:index]
  402. receiver.buffer = receiver.buffer[index+1:]
  403. receiver.callback(notice)
  404. return len(p), nil
  405. }
  406. // NewNoticeConsoleRewriter consumes JSON-format notice input and parses each
  407. // notice and rewrites in a more human-readable format more suitable for
  408. // console output. The data payload field is left as JSON.
  409. func NewNoticeConsoleRewriter(writer io.Writer) *NoticeReceiver {
  410. return NewNoticeReceiver(func(notice []byte) {
  411. var object noticeObject
  412. _ = json.Unmarshal(notice, &object)
  413. fmt.Fprintf(
  414. writer,
  415. "%s %s %s\n",
  416. object.Timestamp,
  417. object.NoticeType,
  418. string(object.Data))
  419. })
  420. }