txn_cross_process.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. package internal
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "time"
  7. "github.com/newrelic/go-agent/internal/cat"
  8. )
  9. // Bitfield values for the TxnCrossProcess.Type field.
  10. const (
  11. txnCrossProcessSynthetics = (1 << 0)
  12. txnCrossProcessInbound = (1 << 1)
  13. txnCrossProcessOutbound = (1 << 2)
  14. )
  15. var (
  16. // ErrAccountNotTrusted indicates that, while the inbound headers were valid,
  17. // the account ID within them is not trusted by the user's application.
  18. ErrAccountNotTrusted = errors.New("account not trusted")
  19. )
  20. // TxnCrossProcess contains the metadata required for CAT and Synthetics
  21. // headers, transaction events, and traces.
  22. type TxnCrossProcess struct {
  23. // The user side switch controlling whether CAT is enabled or not.
  24. Enabled bool
  25. // The user side switch controlling whether Distributed Tracing is enabled or not
  26. // This is required by synthetics support. If Distributed Tracing is enabled,
  27. // any synthetics functionality that is triggered should not set nr.guid.
  28. DistributedTracingEnabled bool
  29. // Rather than copying in the entire ConnectReply, here are the fields that
  30. // we need to support CAT.
  31. CrossProcessID []byte
  32. EncodingKey []byte
  33. TrustedAccounts trustedAccountSet
  34. // CAT state for a given transaction.
  35. Type uint8
  36. ClientID string
  37. GUID string
  38. TripID string
  39. PathHash string
  40. AlternatePathHashes map[string]bool
  41. ReferringPathHash string
  42. ReferringTxnGUID string
  43. Synthetics *cat.SyntheticsHeader
  44. // The encoded synthetics header received as part of the request headers, if
  45. // any. By storing this here, we avoid needing to marshal the invariant
  46. // Synthetics struct above each time an external segment is created.
  47. SyntheticsHeader string
  48. }
  49. // CrossProcessMetadata represents the metadata that must be transmitted with
  50. // an external request for CAT to work.
  51. type CrossProcessMetadata struct {
  52. ID string
  53. TxnData string
  54. Synthetics string
  55. }
  56. // Init initialises a TxnCrossProcess based on the given application connect
  57. // reply.
  58. func (txp *TxnCrossProcess) Init(enabled bool, dt bool, reply *ConnectReply) {
  59. txp.CrossProcessID = []byte(reply.CrossProcessID)
  60. txp.EncodingKey = []byte(reply.EncodingKey)
  61. txp.DistributedTracingEnabled = dt
  62. txp.Enabled = enabled
  63. txp.TrustedAccounts = reply.TrustedAccounts
  64. }
  65. // CreateCrossProcessMetadata generates request metadata that enable CAT and
  66. // Synthetics support for an external segment.
  67. func (txp *TxnCrossProcess) CreateCrossProcessMetadata(txnName, appName string) (CrossProcessMetadata, error) {
  68. metadata := CrossProcessMetadata{}
  69. // Regardless of the user's CAT settings, if there was a synthetics header in
  70. // the inbound request, a synthetics header should always be included in the
  71. // outbound request headers.
  72. if txp.IsSynthetics() {
  73. metadata.Synthetics = txp.SyntheticsHeader
  74. }
  75. if txp.Enabled {
  76. txp.SetOutbound(true)
  77. txp.requireTripID()
  78. id, err := txp.outboundID()
  79. if err != nil {
  80. return metadata, err
  81. }
  82. txnData, err := txp.outboundTxnData(txnName, appName)
  83. if err != nil {
  84. return metadata, err
  85. }
  86. metadata.ID = id
  87. metadata.TxnData = txnData
  88. }
  89. return metadata, nil
  90. }
  91. // Finalise handles any end-of-transaction tasks. In practice, this simply
  92. // means ensuring the path hash is set if it hasn't already been.
  93. func (txp *TxnCrossProcess) Finalise(txnName, appName string) error {
  94. if txp.Enabled && txp.Used() {
  95. _, err := txp.setPathHash(txnName, appName)
  96. return err
  97. }
  98. // If there was no CAT activity, then do nothing, successfully.
  99. return nil
  100. }
  101. // IsInbound returns true if the transaction had inbound CAT headers.
  102. func (txp *TxnCrossProcess) IsInbound() bool {
  103. return 0 != (txp.Type & txnCrossProcessInbound)
  104. }
  105. // IsOutbound returns true if the transaction has generated outbound CAT
  106. // headers.
  107. func (txp *TxnCrossProcess) IsOutbound() bool {
  108. // We don't actually use this anywhere today, but it feels weird not having
  109. // it.
  110. return 0 != (txp.Type & txnCrossProcessOutbound)
  111. }
  112. // IsSynthetics returns true if the transaction had inbound Synthetics headers.
  113. func (txp *TxnCrossProcess) IsSynthetics() bool {
  114. // Technically, this is redundant: the presence of a non-nil Synthetics
  115. // pointer should be sufficient to determine if this is a synthetics
  116. // transaction. Nevertheless, it's convenient to have the Type field be
  117. // non-zero if any CAT behaviour has occurred.
  118. return 0 != (txp.Type&txnCrossProcessSynthetics) && nil != txp.Synthetics
  119. }
  120. // ParseAppData decodes the given appData value.
  121. func (txp *TxnCrossProcess) ParseAppData(encodedAppData string) (*cat.AppDataHeader, error) {
  122. if !txp.Enabled {
  123. return nil, nil
  124. }
  125. if encodedAppData != "" {
  126. rawAppData, err := deobfuscate(encodedAppData, txp.EncodingKey)
  127. if err != nil {
  128. return nil, err
  129. }
  130. appData := &cat.AppDataHeader{}
  131. if err := json.Unmarshal(rawAppData, appData); err != nil {
  132. return nil, err
  133. }
  134. return appData, nil
  135. }
  136. return nil, nil
  137. }
  138. // CreateAppData creates the appData value that should be sent with a response
  139. // to ensure CAT operates as expected.
  140. func (txp *TxnCrossProcess) CreateAppData(name string, queueTime, responseTime time.Duration, contentLength int64) (string, error) {
  141. // If CAT is disabled, do nothing, successfully.
  142. if !txp.Enabled {
  143. return "", nil
  144. }
  145. data, err := json.Marshal(&cat.AppDataHeader{
  146. CrossProcessID: string(txp.CrossProcessID),
  147. TransactionName: name,
  148. QueueTimeInSeconds: queueTime.Seconds(),
  149. ResponseTimeInSeconds: responseTime.Seconds(),
  150. ContentLength: contentLength,
  151. TransactionGUID: txp.GUID,
  152. })
  153. if err != nil {
  154. return "", err
  155. }
  156. obfuscated, err := obfuscate(data, txp.EncodingKey)
  157. if err != nil {
  158. return "", err
  159. }
  160. return obfuscated, nil
  161. }
  162. // Used returns true if any CAT or Synthetics related functionality has been
  163. // triggered on the transaction.
  164. func (txp *TxnCrossProcess) Used() bool {
  165. return 0 != txp.Type
  166. }
  167. // SetInbound sets the inbound CAT flag. This function is provided only for
  168. // internal and unit testing purposes, and should not be used outside of this
  169. // package normally.
  170. func (txp *TxnCrossProcess) SetInbound(inbound bool) {
  171. if inbound {
  172. txp.Type |= txnCrossProcessInbound
  173. } else {
  174. txp.Type &^= txnCrossProcessInbound
  175. }
  176. }
  177. // SetOutbound sets the outbound CAT flag. This function is provided only for
  178. // internal and unit testing purposes, and should not be used outside of this
  179. // package normally.
  180. func (txp *TxnCrossProcess) SetOutbound(outbound bool) {
  181. if outbound {
  182. txp.Type |= txnCrossProcessOutbound
  183. } else {
  184. txp.Type &^= txnCrossProcessOutbound
  185. }
  186. }
  187. // SetSynthetics sets the Synthetics CAT flag. This function is provided only
  188. // for internal and unit testing purposes, and should not be used outside of
  189. // this package normally.
  190. func (txp *TxnCrossProcess) SetSynthetics(synthetics bool) {
  191. if synthetics {
  192. txp.Type |= txnCrossProcessSynthetics
  193. } else {
  194. txp.Type &^= txnCrossProcessSynthetics
  195. }
  196. }
  197. // handleInboundRequestHeaders parses the CAT headers from the given metadata
  198. // and updates the relevant fields on the provided TxnData.
  199. func (txp *TxnCrossProcess) handleInboundRequestHeaders(metadata CrossProcessMetadata) error {
  200. if txp.Enabled && metadata.ID != "" && metadata.TxnData != "" {
  201. if err := txp.handleInboundRequestEncodedCAT(metadata.ID, metadata.TxnData); err != nil {
  202. return err
  203. }
  204. }
  205. if metadata.Synthetics != "" {
  206. if err := txp.handleInboundRequestEncodedSynthetics(metadata.Synthetics); err != nil {
  207. return err
  208. }
  209. }
  210. return nil
  211. }
  212. func (txp *TxnCrossProcess) handleInboundRequestEncodedCAT(encodedID, encodedTxnData string) error {
  213. rawID, err := deobfuscate(encodedID, txp.EncodingKey)
  214. if err != nil {
  215. return err
  216. }
  217. rawTxnData, err := deobfuscate(encodedTxnData, txp.EncodingKey)
  218. if err != nil {
  219. return err
  220. }
  221. if err := txp.handleInboundRequestID(rawID); err != nil {
  222. return err
  223. }
  224. return txp.handleInboundRequestTxnData(rawTxnData)
  225. }
  226. func (txp *TxnCrossProcess) handleInboundRequestID(raw []byte) error {
  227. id, err := cat.NewIDHeader(raw)
  228. if err != nil {
  229. return err
  230. }
  231. if !txp.TrustedAccounts.IsTrusted(id.AccountID) {
  232. return ErrAccountNotTrusted
  233. }
  234. txp.SetInbound(true)
  235. txp.ClientID = string(raw)
  236. txp.setRequireGUID()
  237. return nil
  238. }
  239. func (txp *TxnCrossProcess) handleInboundRequestTxnData(raw []byte) error {
  240. txnData := &cat.TxnDataHeader{}
  241. if err := json.Unmarshal(raw, txnData); err != nil {
  242. return err
  243. }
  244. txp.SetInbound(true)
  245. if txnData.TripID != "" {
  246. txp.TripID = txnData.TripID
  247. } else {
  248. txp.setRequireGUID()
  249. txp.TripID = txp.GUID
  250. }
  251. txp.ReferringTxnGUID = txnData.GUID
  252. txp.ReferringPathHash = txnData.PathHash
  253. return nil
  254. }
  255. func (txp *TxnCrossProcess) handleInboundRequestEncodedSynthetics(encoded string) error {
  256. raw, err := deobfuscate(encoded, txp.EncodingKey)
  257. if err != nil {
  258. return err
  259. }
  260. if err := txp.handleInboundRequestSynthetics(raw); err != nil {
  261. return err
  262. }
  263. txp.SyntheticsHeader = encoded
  264. return nil
  265. }
  266. func (txp *TxnCrossProcess) handleInboundRequestSynthetics(raw []byte) error {
  267. synthetics := &cat.SyntheticsHeader{}
  268. if err := json.Unmarshal(raw, synthetics); err != nil {
  269. return err
  270. }
  271. // The specced behaviour here if the account isn't trusted is to disable the
  272. // synthetics handling, but not CAT in general, so we won't return an error
  273. // here.
  274. if txp.TrustedAccounts.IsTrusted(synthetics.AccountID) {
  275. txp.SetSynthetics(true)
  276. txp.setRequireGUID()
  277. txp.Synthetics = synthetics
  278. }
  279. return nil
  280. }
  281. func (txp *TxnCrossProcess) outboundID() (string, error) {
  282. return obfuscate(txp.CrossProcessID, txp.EncodingKey)
  283. }
  284. func (txp *TxnCrossProcess) outboundTxnData(txnName, appName string) (string, error) {
  285. pathHash, err := txp.setPathHash(txnName, appName)
  286. if err != nil {
  287. return "", err
  288. }
  289. data, err := json.Marshal(&cat.TxnDataHeader{
  290. GUID: txp.GUID,
  291. TripID: txp.TripID,
  292. PathHash: pathHash,
  293. })
  294. if err != nil {
  295. return "", err
  296. }
  297. return obfuscate(data, txp.EncodingKey)
  298. }
  299. // setRequireGUID ensures that the transaction has a valid GUID, and sets the
  300. // nr.guid and trip ID if they are not already set. If the customer has enabled
  301. // DistributedTracing, then the new style of guid will be set elsewhere.
  302. func (txp *TxnCrossProcess) setRequireGUID() {
  303. if txp.DistributedTracingEnabled {
  304. return
  305. }
  306. if txp.GUID != "" {
  307. return
  308. }
  309. txp.GUID = fmt.Sprintf("%x", RandUint64())
  310. if txp.TripID == "" {
  311. txp.requireTripID()
  312. }
  313. }
  314. // requireTripID ensures that the transaction has a valid trip ID.
  315. func (txp *TxnCrossProcess) requireTripID() {
  316. if !txp.Enabled {
  317. return
  318. }
  319. if txp.TripID != "" {
  320. return
  321. }
  322. txp.setRequireGUID()
  323. txp.TripID = txp.GUID
  324. }
  325. // setPathHash generates a path hash, sets the transaction's path hash to
  326. // match, and returns it. This function will also ensure that the alternate
  327. // path hashes are correctly updated.
  328. func (txp *TxnCrossProcess) setPathHash(txnName, appName string) (string, error) {
  329. pathHash, err := cat.GeneratePathHash(txp.ReferringPathHash, txnName, appName)
  330. if err != nil {
  331. return "", err
  332. }
  333. if pathHash != txp.PathHash {
  334. if txp.PathHash != "" {
  335. // Lazily initialise the alternate path hashes if they haven't been
  336. // already.
  337. if txp.AlternatePathHashes == nil {
  338. txp.AlternatePathHashes = make(map[string]bool)
  339. }
  340. // The spec limits us to a maximum of 10 alternate path hashes.
  341. if len(txp.AlternatePathHashes) < 10 {
  342. txp.AlternatePathHashes[txp.PathHash] = true
  343. }
  344. }
  345. txp.PathHash = pathHash
  346. }
  347. return pathHash, nil
  348. }